mirror of
https://github.com/openai/codex.git
synced 2026-11-07 16:35:32 +00:00
Unify turn input submission and routing (#38275)
## What changed - Add `TurnInputRequest` and typed submission results for atomically starting a turn, steering the active turn, or declining input with a specific reason. - Expose `start_or_steer_turn`, `start_turn_if_idle`, and `steer_turn` on `CodexThread`, and migrate Core consumers to these APIs. - Make app-server `turn/start` steer an active regular turn and return that turn's ID. Reject incompatible output schemas and non-steerable turns without applying settings or enqueueing input. ## Testing - Cover concurrent start-or-steer submissions, accepted and rejected settings updates, output-schema compatibility, idle-start rejection, and app-server steering. GitOrigin-RevId: dd9b5528d76ec650c019e97af420bc13190ea86a
This commit is contained in:
1 parent
4b07886d59
commit
cbb7e82a8b
137 files changed
+5217
-6812
No files matched your search
Generated
-1
@@ -3603,7 +3603,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
|
||||||
"wiremock",
|
"wiremock",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
|||||||
&& span_attr(span, "rpc.method") == Some("turn/start")
|
&& span_attr(span, "rpc.method") == Some("turn/start")
|
||||||
&& span.span_context.trace_id() == remote_trace_id
|
&& span.span_context.trace_id() == remote_trace_id
|
||||||
}) && spans.iter().any(|span| {
|
}) && spans.iter().any(|span| {
|
||||||
span_attr(span, "codex.op") == Some("user_input")
|
span_attr(span, "codex.op") == Some("turn_input")
|
||||||
&& span.span_context.trace_id() == remote_trace_id
|
&& span.span_context.trace_id() == remote_trace_id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -697,8 +697,8 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
|||||||
let server_request_span =
|
let server_request_span =
|
||||||
find_rpc_span_with_trace(&spans, SpanKind::Server, "turn/start", remote_trace_id);
|
find_rpc_span_with_trace(&spans, SpanKind::Server, "turn/start", remote_trace_id);
|
||||||
let core_turn_span =
|
let core_turn_span =
|
||||||
find_span_with_trace(&spans, remote_trace_id, "codex.op=user_input", |span| {
|
find_span_with_trace(&spans, remote_trace_id, "codex.op=turn_input", |span| {
|
||||||
span_attr(span, "codex.op") == Some("user_input")
|
span_attr(span, "codex.op") == Some("turn_input")
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(server_request_span.parent_span_id, remote_parent_span_id);
|
assert_eq!(server_request_span.parent_span_id, remote_parent_span_id);
|
||||||
|
|||||||
@@ -333,12 +333,17 @@ use codex_core::CodexThreadSettingsOverrides;
|
|||||||
use codex_core::ForkSnapshot;
|
use codex_core::ForkSnapshot;
|
||||||
use codex_core::McpManager;
|
use codex_core::McpManager;
|
||||||
use codex_core::NewThread;
|
use codex_core::NewThread;
|
||||||
|
use codex_core::NotSubmittedReason;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use codex_core::SessionMeta;
|
use codex_core::SessionMeta;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
use codex_core::SteerInputError;
|
use codex_core::SteerSubmission;
|
||||||
use codex_core::ThreadConfigSnapshot;
|
use codex_core::ThreadConfigSnapshot;
|
||||||
use codex_core::ThreadManager;
|
use codex_core::ThreadManager;
|
||||||
|
use codex_core::TurnInput;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
|
use codex_core::TurnInputSubmission;
|
||||||
|
use codex_core::TurnStartOptions;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::ConfigOverrides;
|
use codex_core::config::ConfigOverrides;
|
||||||
use codex_core::config::NetworkProxyAuditMetadata;
|
use codex_core::config::NetworkProxyAuditMetadata;
|
||||||
|
|||||||
@@ -554,28 +554,38 @@ impl TurnRequestProcessor {
|
|||||||
.map(PermissionProfile::from_legacy_sandbox_policy)
|
.map(PermissionProfile::from_legacy_sandbox_policy)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the turn by submitting the user input. Return its submission id as turn_id.
|
let submission = thread
|
||||||
let turn_op = Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: mapped_items,
|
TurnInputRequest::new(TurnInput::UserInput {
|
||||||
final_output_json_schema: params.output_schema,
|
content: mapped_items,
|
||||||
responsesapi_client_metadata: params.responsesapi_client_metadata,
|
client_id: client_user_message_id,
|
||||||
additional_context,
|
})
|
||||||
thread_settings,
|
.with_thread_settings(thread_settings)
|
||||||
};
|
.on_start(TurnStartOptions {
|
||||||
let turn_id = thread
|
final_output_json_schema: params.output_schema,
|
||||||
.submit_user_input_with_client_user_message_id(
|
..Default::default()
|
||||||
turn_op,
|
})
|
||||||
self.request_trace_context(&request_id).await,
|
.with_additional_context(additional_context)
|
||||||
client_user_message_id,
|
.with_responses_metadata(params.responsesapi_client_metadata)
|
||||||
|
.with_trace(self.request_trace_context(&request_id).await),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
let error = internal_error(format!("failed to start turn: {err}"));
|
let error = internal_error(format!("failed to submit turn input: {err}"));
|
||||||
self.track_error_response(&request_id, &error, /*error_type*/ None);
|
self.track_error_response(&request_id, &error, /*error_type*/ None);
|
||||||
error
|
error
|
||||||
})?;
|
})?;
|
||||||
|
let (turn_id, started) = match submission {
|
||||||
|
TurnInputSubmission::Started { turn_id } => (turn_id, true),
|
||||||
|
TurnInputSubmission::Steered { turn_id } => (turn_id, false),
|
||||||
|
TurnInputSubmission::NotSubmitted { reason } => {
|
||||||
|
let error = internal_error(format!("failed to submit turn input: {reason:?}"));
|
||||||
|
self.track_error_response(&request_id, &error, /*error_type*/ None);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if turn_has_input {
|
if turn_has_input && started {
|
||||||
let config_snapshot = thread.config_snapshot().await;
|
let config_snapshot = thread.config_snapshot().await;
|
||||||
let parent_permission_profile =
|
let parent_permission_profile =
|
||||||
parent_permission_profile_override.unwrap_or(config_snapshot.permission_profile);
|
parent_permission_profile_override.unwrap_or(config_snapshot.permission_profile);
|
||||||
@@ -943,32 +953,41 @@ impl TurnRequestProcessor {
|
|||||||
.collect();
|
.collect();
|
||||||
let additional_context = map_additional_context(params.additional_context);
|
let additional_context = map_additional_context(params.additional_context);
|
||||||
|
|
||||||
let turn_id = thread
|
let submission = thread
|
||||||
.steer_input(
|
.steer_turn(
|
||||||
mapped_items,
|
TurnInputRequest::new(TurnInput::UserInput {
|
||||||
additional_context,
|
content: mapped_items,
|
||||||
Some(¶ms.expected_turn_id),
|
client_id: params.client_user_message_id,
|
||||||
params.client_user_message_id,
|
})
|
||||||
params.responsesapi_client_metadata,
|
.with_additional_context(additional_context)
|
||||||
|
.with_responses_metadata(params.responsesapi_client_metadata),
|
||||||
|
params.expected_turn_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
let (message, data, error_type) = match err {
|
let error = internal_error(format!("failed to steer turn: {err}"));
|
||||||
SteerInputError::NoActiveTurn(_) => (
|
self.track_error_response(request_id, &error, /*error_type*/ None);
|
||||||
|
error
|
||||||
|
})?;
|
||||||
|
let turn_id = match submission {
|
||||||
|
SteerSubmission::Steered { turn_id } => turn_id,
|
||||||
|
SteerSubmission::NotSubmitted { reason } => {
|
||||||
|
let (message, data, error_type) = match reason {
|
||||||
|
NotSubmittedReason::NoActiveTurn | NotSubmittedReason::NotIdle => (
|
||||||
"no active turn to steer".to_string(),
|
"no active turn to steer".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(AnalyticsJsonRpcError::TurnSteer(
|
Some(AnalyticsJsonRpcError::TurnSteer(
|
||||||
TurnSteerRequestError::NoActiveTurn,
|
TurnSteerRequestError::NoActiveTurn,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
SteerInputError::ExpectedTurnMismatch { expected, actual } => (
|
NotSubmittedReason::ExpectedTurnMismatch { expected, actual } => (
|
||||||
format!("expected active turn id `{expected}` but found `{actual}`"),
|
format!("expected active turn id `{expected}` but found `{actual}`"),
|
||||||
None,
|
None,
|
||||||
Some(AnalyticsJsonRpcError::TurnSteer(
|
Some(AnalyticsJsonRpcError::TurnSteer(
|
||||||
TurnSteerRequestError::ExpectedTurnMismatch,
|
TurnSteerRequestError::ExpectedTurnMismatch,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
SteerInputError::ActiveTurnNotSteerable { turn_kind } => {
|
NotSubmittedReason::ActiveTurnNotSteerable { turn_kind } => {
|
||||||
let (message, turn_steer_error) = match turn_kind {
|
let (message, turn_steer_error) = match turn_kind {
|
||||||
codex_protocol::protocol::NonSteerableTurnKind::Review => (
|
codex_protocol::protocol::NonSteerableTurnKind::Review => (
|
||||||
"cannot steer a review turn".to_string(),
|
"cannot steer a review turn".to_string(),
|
||||||
@@ -1002,17 +1021,30 @@ impl TurnRequestProcessor {
|
|||||||
Some(AnalyticsJsonRpcError::TurnSteer(turn_steer_error)),
|
Some(AnalyticsJsonRpcError::TurnSteer(turn_steer_error)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SteerInputError::EmptyInput => (
|
NotSubmittedReason::EmptyInput => (
|
||||||
"input must not be empty".to_string(),
|
"input must not be empty".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(AnalyticsJsonRpcError::Input(InputError::Empty)),
|
Some(AnalyticsJsonRpcError::Input(InputError::Empty)),
|
||||||
),
|
),
|
||||||
|
NotSubmittedReason::ActiveTurnOutputSchemaMismatch => (
|
||||||
|
"active turn uses a different output schema".to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
NotSubmittedReason::PendingTriggerTurn | NotSubmittedReason::PlanMode => (
|
||||||
|
"no active turn to steer".to_string(),
|
||||||
|
None,
|
||||||
|
Some(AnalyticsJsonRpcError::TurnSteer(
|
||||||
|
TurnSteerRequestError::NoActiveTurn,
|
||||||
|
)),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let mut error = invalid_request(message);
|
let mut error = invalid_request(message);
|
||||||
error.data = data;
|
error.data = data;
|
||||||
self.track_error_response(request_id, &error, error_type);
|
self.track_error_response(request_id, &error, error_type);
|
||||||
error
|
return Err(error);
|
||||||
})?;
|
}
|
||||||
|
};
|
||||||
Ok(TurnSteerResponse { turn_id })
|
Ok(TurnSteerResponse { turn_id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,12 +81,15 @@ use core_test_support::responses;
|
|||||||
use core_test_support::skip_if_no_network;
|
use core_test_support::skip_if_no_network;
|
||||||
use core_test_support::skip_if_remote;
|
use core_test_support::skip_if_remote;
|
||||||
use core_test_support::skip_if_wine_exec;
|
use core_test_support::skip_if_wine_exec;
|
||||||
|
use core_test_support::streaming_sse::StreamingSseChunk;
|
||||||
|
use core_test_support::streaming_sse::start_streaming_sse_server;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
use tokio::sync::oneshot;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use wiremock::ResponseTemplate;
|
use wiremock::ResponseTemplate;
|
||||||
|
|
||||||
@@ -288,6 +291,89 @@ async fn turn_start_with_empty_input_runs_model_request() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn turn_start_steers_active_turn_and_returns_active_turn_id() -> Result<()> {
|
||||||
|
let (release_response, response_gate) = oneshot::channel();
|
||||||
|
let (server, _completions) = start_streaming_sse_server(vec![
|
||||||
|
vec![
|
||||||
|
StreamingSseChunk {
|
||||||
|
gate: None,
|
||||||
|
body: responses::sse(vec![responses::ev_response_created("resp-1")]),
|
||||||
|
},
|
||||||
|
StreamingSseChunk {
|
||||||
|
gate: Some(response_gate),
|
||||||
|
body: responses::sse(vec![responses::ev_completed("resp-1")]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![StreamingSseChunk {
|
||||||
|
gate: None,
|
||||||
|
body: responses::sse(vec![
|
||||||
|
responses::ev_response_created("resp-2"),
|
||||||
|
responses::ev_completed("resp-2"),
|
||||||
|
]),
|
||||||
|
}],
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
MockResponsesConfig::new(server.uri()).write(codex_home.path())?;
|
||||||
|
let mut mcp = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let ThreadStartResponse { thread, .. } = mcp
|
||||||
|
.start_thread(ThreadStartParams {
|
||||||
|
model: Some("mock-model".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let TurnStartResponse { turn: active_turn } = mcp
|
||||||
|
.request(|request_id| ClientRequest::TurnStart {
|
||||||
|
request_id,
|
||||||
|
params: TurnStartParams {
|
||||||
|
thread_id: thread.id.clone(),
|
||||||
|
client_user_message_id: None,
|
||||||
|
input: vec![V2UserInput::Text {
|
||||||
|
text: "start".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_notification_message("turn/started"),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let TurnStartResponse { turn: steered_turn } = mcp
|
||||||
|
.request(|request_id| ClientRequest::TurnStart {
|
||||||
|
request_id,
|
||||||
|
params: TurnStartParams {
|
||||||
|
thread_id: thread.id.clone(),
|
||||||
|
client_user_message_id: None,
|
||||||
|
input: vec![V2UserInput::Text {
|
||||||
|
text: "steer".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(steered_turn.id, active_turn.id);
|
||||||
|
|
||||||
|
release_response
|
||||||
|
.send(())
|
||||||
|
.expect("active response gate should remain open");
|
||||||
|
timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_notification_message("turn/completed"),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn turn_start_additional_context_flows_to_model_input() -> Result<()> {
|
async fn turn_start_additional_context_flows_to_model_input() -> Result<()> {
|
||||||
let responses = vec![create_final_assistant_message_sse_response("Done")?];
|
let responses = vec![create_final_assistant_message_sse_response("Done")?];
|
||||||
|
|||||||
@@ -32,11 +32,17 @@ pub use codex_core::ForkSnapshot;
|
|||||||
pub use codex_core::LoadedAgentsMd;
|
pub use codex_core::LoadedAgentsMd;
|
||||||
pub use codex_core::McpManager;
|
pub use codex_core::McpManager;
|
||||||
pub use codex_core::NewThread;
|
pub use codex_core::NewThread;
|
||||||
|
pub use codex_core::NotSubmittedReason;
|
||||||
|
pub use codex_core::StartIfIdleSubmission;
|
||||||
pub use codex_core::StartThreadOptions;
|
pub use codex_core::StartThreadOptions;
|
||||||
pub use codex_core::StateDbHandle;
|
pub use codex_core::StateDbHandle;
|
||||||
|
pub use codex_core::SteerSubmission;
|
||||||
pub use codex_core::ThreadManager;
|
pub use codex_core::ThreadManager;
|
||||||
pub use codex_core::ThreadShutdownReport;
|
pub use codex_core::ThreadShutdownReport;
|
||||||
pub use codex_core::UserMessageAdmission;
|
pub use codex_core::TurnInput;
|
||||||
|
pub use codex_core::TurnInputRequest;
|
||||||
|
pub use codex_core::TurnInputSubmission;
|
||||||
|
pub use codex_core::TurnStartOptions;
|
||||||
pub use codex_core::WaitForEnvironmentToolConfig;
|
pub use codex_core::WaitForEnvironmentToolConfig;
|
||||||
pub use codex_core::build_models_manager;
|
pub use codex_core::build_models_manager;
|
||||||
pub use codex_core::config::Config;
|
pub use codex_core::config::Config;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use crate::TurnInputRequest;
|
||||||
|
use crate::TurnInputSubmission;
|
||||||
|
use crate::TurnStartOptions;
|
||||||
use crate::agent::AgentStatus;
|
use crate::agent::AgentStatus;
|
||||||
use crate::agent::registry::AgentMetadata;
|
use crate::agent::registry::AgentMetadata;
|
||||||
use crate::agent::registry::AgentRegistry;
|
use crate::agent::registry::AgentRegistry;
|
||||||
@@ -51,6 +54,7 @@ use std::sync::Arc;
|
|||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(crate) use self::execution::AgentExecutionGuard;
|
pub(crate) use self::execution::AgentExecutionGuard;
|
||||||
use self::execution::AgentExecutionLimiter;
|
use self::execution::AgentExecutionLimiter;
|
||||||
@@ -173,28 +177,32 @@ impl AgentControl {
|
|||||||
root_turn_id: Option<String>,
|
root_turn_id: Option<String>,
|
||||||
) -> CodexResult<String> {
|
) -> CodexResult<String> {
|
||||||
let state = self.upgrade()?;
|
let state = self.upgrade()?;
|
||||||
self.ensure_execution_capacity_for_turn_start(agent_id, /*starts_turn*/ true)
|
let thread = state.get_thread(agent_id).await?;
|
||||||
.await?;
|
let result = match thread
|
||||||
self.send_input_after_capacity_check(agent_id, &state, input, parent_turn_id, root_turn_id)
|
.start_or_steer_turn(
|
||||||
|
TurnInputRequest::user_input(input).on_start(TurnStartOptions {
|
||||||
|
parent_turn_id,
|
||||||
|
root_turn_id,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(TurnInputSubmission::Started { turn_id }) => Ok(turn_id),
|
||||||
|
Ok(TurnInputSubmission::Steered { .. }) => {
|
||||||
|
// MAv1 exposes an opaque `submission_id` to the model. The legacy
|
||||||
|
// `Op::UserInput` path returned a fresh ID for every steer, while the
|
||||||
|
// turn-input API returns the active turn ID. Keep the tool-visible ID
|
||||||
|
// unique without adding a submission receipt back to Core.
|
||||||
|
Ok(Uuid::now_v7().to_string())
|
||||||
|
}
|
||||||
|
Ok(TurnInputSubmission::NotSubmitted { reason }) => Err(CodexErr::InvalidRequest(
|
||||||
|
format!("turn input was not submitted: {reason:?}"),
|
||||||
|
)),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
};
|
||||||
|
self.handle_thread_request_result(agent_id, &state, result)
|
||||||
.await
|
.await
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_input_after_capacity_check(
|
|
||||||
&self,
|
|
||||||
agent_id: ThreadId,
|
|
||||||
state: &Arc<ThreadManagerState>,
|
|
||||||
input: Vec<UserInput>,
|
|
||||||
parent_turn_id: Option<String>,
|
|
||||||
root_turn_id: Option<String>,
|
|
||||||
) -> CodexResult<String> {
|
|
||||||
self.handle_thread_request_result(
|
|
||||||
agent_id,
|
|
||||||
state,
|
|
||||||
state
|
|
||||||
.send_op(agent_id, input.into(), parent_turn_id, root_turn_id)
|
|
||||||
.await,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn send_inter_agent_communication(
|
pub(crate) async fn send_inter_agent_communication(
|
||||||
@@ -206,8 +214,11 @@ impl AgentControl {
|
|||||||
root_turn_id: Option<String>,
|
root_turn_id: Option<String>,
|
||||||
) -> CodexResult<String> {
|
) -> CodexResult<String> {
|
||||||
let state = self.upgrade()?;
|
let state = self.upgrade()?;
|
||||||
self.ensure_execution_capacity_for_turn_start(agent_id, communication.trigger_turn)
|
if communication.trigger_turn {
|
||||||
.await?;
|
let thread = state.get_thread(agent_id).await?;
|
||||||
|
self.ensure_execution_capacity_for_turn_start(&thread)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
self.send_inter_agent_communication_after_capacity_check(
|
self.send_inter_agent_communication_after_capacity_check(
|
||||||
agent_id,
|
agent_id,
|
||||||
&state,
|
&state,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use super::AgentControl;
|
use super::AgentControl;
|
||||||
use codex_protocol::ThreadId;
|
use crate::codex_thread::CodexThread;
|
||||||
use codex_protocol::error::CodexErr;
|
use codex_protocol::error::CodexErr;
|
||||||
use codex_protocol::error::CodexErrorDetails;
|
use codex_protocol::error::CodexErrorDetails;
|
||||||
use codex_protocol::error::Result as CodexResult;
|
use codex_protocol::error::Result as CodexResult;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
@@ -28,25 +27,10 @@ impl Drop for AgentExecutionGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentControl {
|
impl AgentControl {
|
||||||
pub(crate) async fn ensure_execution_capacity_for_op(
|
pub(crate) async fn ensure_execution_capacity_for_turn_start(
|
||||||
&self,
|
&self,
|
||||||
thread_id: ThreadId,
|
thread: &CodexThread,
|
||||||
op: &Op,
|
|
||||||
) -> CodexResult<()> {
|
) -> CodexResult<()> {
|
||||||
self.ensure_execution_capacity_for_turn_start(thread_id, op_starts_turn(op))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn ensure_execution_capacity_for_turn_start(
|
|
||||||
&self,
|
|
||||||
thread_id: ThreadId,
|
|
||||||
starts_turn: bool,
|
|
||||||
) -> CodexResult<()> {
|
|
||||||
if !starts_turn {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let state = self.upgrade()?;
|
|
||||||
let thread = state.get_thread(thread_id).await?;
|
|
||||||
if thread.session.active_turn.lock().await.is_some() {
|
if thread.session.active_turn.lock().await.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -104,11 +88,6 @@ impl AgentExecutionLimiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn op_starts_turn(op: &Op) -> bool {
|
|
||||||
matches!(op, Op::UserInput { .. })
|
|
||||||
|| matches!(op, Op::InterAgentCommunication { communication } if communication.trigger_turn)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_execution_limited(
|
fn is_execution_limited(
|
||||||
multi_agent_version: MultiAgentVersion,
|
multi_agent_version: MultiAgentVersion,
|
||||||
session_source: &SessionSource,
|
session_source: &SessionSource,
|
||||||
|
|||||||
@@ -544,9 +544,8 @@ impl AgentControl {
|
|||||||
|
|
||||||
match initial_input {
|
match initial_input {
|
||||||
SpawnInitialInput::UserInput(input) => {
|
SpawnInitialInput::UserInput(input) => {
|
||||||
self.send_input_after_capacity_check(
|
self.send_input(
|
||||||
new_thread.thread_id,
|
new_thread.thread_id,
|
||||||
&state,
|
|
||||||
input,
|
input,
|
||||||
options.parent_turn_id,
|
options.parent_turn_id,
|
||||||
options.root_turn_id,
|
options.root_turn_id,
|
||||||
|
|||||||
@@ -98,28 +98,6 @@ fn captured_op_matches(actual: &(ThreadId, Op), expected: &(ThreadId, Op)) -> bo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
match (&actual.1, &expected.1) {
|
match (&actual.1, &expected.1) {
|
||||||
(
|
|
||||||
Op::UserInput {
|
|
||||||
items: actual_items,
|
|
||||||
final_output_json_schema: actual_schema,
|
|
||||||
responsesapi_client_metadata: actual_metadata,
|
|
||||||
additional_context: actual_context,
|
|
||||||
thread_settings: actual_settings,
|
|
||||||
},
|
|
||||||
Op::UserInput {
|
|
||||||
items: expected_items,
|
|
||||||
final_output_json_schema: expected_schema,
|
|
||||||
responsesapi_client_metadata: expected_metadata,
|
|
||||||
additional_context: expected_context,
|
|
||||||
thread_settings: expected_settings,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
actual_items == expected_items
|
|
||||||
&& actual_schema == expected_schema
|
|
||||||
&& actual_metadata == expected_metadata
|
|
||||||
&& actual_context == expected_context
|
|
||||||
&& actual_settings == expected_settings
|
|
||||||
}
|
|
||||||
(
|
(
|
||||||
Op::InterAgentCommunication {
|
Op::InterAgentCommunication {
|
||||||
communication: actual,
|
communication: actual,
|
||||||
@@ -314,6 +292,29 @@ fn history_contains_text<'a>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_recorded_user_message(thread: &CodexThread, needle: &str) {
|
||||||
|
timeout(Duration::from_secs(5), async {
|
||||||
|
loop {
|
||||||
|
let event = thread
|
||||||
|
.next_event()
|
||||||
|
.await
|
||||||
|
.expect("event stream should stay open");
|
||||||
|
if let EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||||
|
item: TurnItem::UserMessage(item),
|
||||||
|
..
|
||||||
|
}) = event.msg
|
||||||
|
&& item.content.iter().any(
|
||||||
|
|input| matches!(input, UserInput::Text { text, .. } if text.contains(needle)),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("timed out waiting for user message recording");
|
||||||
|
}
|
||||||
|
|
||||||
fn history_contains_assistant_inter_agent_communication<'a>(
|
fn history_contains_assistant_inter_agent_communication<'a>(
|
||||||
history_items: impl IntoIterator<Item = &'a ResponseItem>,
|
history_items: impl IntoIterator<Item = &'a ResponseItem>,
|
||||||
expected: &InterAgentCommunication,
|
expected: &InterAgentCommunication,
|
||||||
@@ -355,6 +356,13 @@ async fn wait_for_subagent_notification(parent_thread: &Arc<CodexThread>) -> boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn persist_thread_for_tree_resume(thread: &Arc<CodexThread>, message: &str) {
|
async fn persist_thread_for_tree_resume(thread: &Arc<CodexThread>, message: &str) {
|
||||||
|
// These tests only need a durable resume fixture. Stop the child prompt
|
||||||
|
// first so this marker records directly instead of waiting behind an
|
||||||
|
// unrelated active turn.
|
||||||
|
thread
|
||||||
|
.session
|
||||||
|
.abort_all_tasks(TurnAbortReason::Interrupted)
|
||||||
|
.await;
|
||||||
thread
|
thread
|
||||||
.inject_user_message_without_turn(message.to_string())
|
.inject_user_message_without_turn(message.to_string())
|
||||||
.await;
|
.await;
|
||||||
@@ -597,7 +605,7 @@ async fn subscribe_status_updates_on_shutdown() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn send_input_submits_user_message() {
|
async fn send_input_submits_user_message() {
|
||||||
let harness = AgentControlHarness::new().await;
|
let harness = AgentControlHarness::new().await;
|
||||||
let (thread_id, _thread) = harness.start_thread().await;
|
let (thread_id, thread) = harness.start_thread().await;
|
||||||
|
|
||||||
let submission_id = harness
|
let submission_id = harness
|
||||||
.control
|
.control
|
||||||
@@ -613,25 +621,7 @@ async fn send_input_submits_user_message() {
|
|||||||
.await
|
.await
|
||||||
.expect("send_input should succeed");
|
.expect("send_input should succeed");
|
||||||
assert!(!submission_id.is_empty());
|
assert!(!submission_id.is_empty());
|
||||||
let expected = (
|
wait_for_recorded_user_message(thread.as_ref(), "hello from tests").await;
|
||||||
thread_id,
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text: "hello from tests".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let captured = harness
|
|
||||||
.manager
|
|
||||||
.captured_ops()
|
|
||||||
.into_iter()
|
|
||||||
.find(|entry| captured_op_matches(entry, &expected));
|
|
||||||
assert!(captured.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -968,30 +958,12 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("spawn_agent should succeed");
|
.expect("spawn_agent should succeed");
|
||||||
let _thread = harness
|
let thread = harness
|
||||||
.manager
|
.manager
|
||||||
.get_thread(thread_id)
|
.get_thread(thread_id)
|
||||||
.await
|
.await
|
||||||
.expect("thread should be registered");
|
.expect("thread should be registered");
|
||||||
let expected = (
|
wait_for_recorded_user_message(thread.as_ref(), "spawned").await;
|
||||||
thread_id,
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text: "spawned".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let captured = harness
|
|
||||||
.manager
|
|
||||||
.captured_ops()
|
|
||||||
.into_iter()
|
|
||||||
.find(|entry| captured_op_matches(entry, &expected));
|
|
||||||
assert!(captured.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1587,25 +1559,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
|||||||
"empty child developer instructions should preserve unrelated developer fragments"
|
"empty child developer instructions should preserve unrelated developer fragments"
|
||||||
);
|
);
|
||||||
|
|
||||||
let expected = (
|
wait_for_recorded_user_message(child_thread.as_ref(), "child task").await;
|
||||||
child_thread_id,
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text: "child task".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let captured = harness
|
|
||||||
.manager
|
|
||||||
.captured_ops()
|
|
||||||
.into_iter()
|
|
||||||
.find(|entry| captured_op_matches(entry, &expected));
|
|
||||||
assert!(captured.is_some());
|
|
||||||
|
|
||||||
let _ = harness
|
let _ = harness
|
||||||
.control
|
.control
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ use codex_login::AuthManager;
|
|||||||
use codex_models_manager::manager::SharedModelsManager;
|
use codex_models_manager::manager::SharedModelsManager;
|
||||||
use codex_protocol::error::CodexErr;
|
use codex_protocol::error::CodexErr;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
|
use codex_protocol::turn_input::TurnStartOptions;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::session::completed_session_loop_termination;
|
use crate::session::completed_session_loop_termination;
|
||||||
@@ -190,19 +194,24 @@ pub(crate) async fn run_codex_thread_one_shot(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send the initial input to kick off the one-shot turn.
|
// Send the initial input to kick off the one-shot turn.
|
||||||
io.submit_with_trace(
|
let submission = io
|
||||||
Op::UserInput {
|
.submit_turn_input(
|
||||||
items: input,
|
TurnInputRequest::user_input(input).on_start(TurnStartOptions {
|
||||||
final_output_json_schema,
|
final_output_json_schema,
|
||||||
responsesapi_client_metadata: None,
|
parent_turn_id: Some(parent_turn_id),
|
||||||
additional_context: Default::default(),
|
root_turn_id,
|
||||||
thread_settings: Default::default(),
|
}),
|
||||||
},
|
TurnInputMode::StartIfIdle,
|
||||||
/*trace*/ None,
|
)
|
||||||
Some(parent_turn_id),
|
.await?;
|
||||||
root_turn_id,
|
match submission {
|
||||||
)
|
TurnInputSubmission::Started { .. } => {}
|
||||||
.await?;
|
submission => {
|
||||||
|
return Err(CodexErr::InvalidRequest(format!(
|
||||||
|
"delegate turn input was not started: {submission:?}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bridge events so we can observe completion and shut down automatically.
|
// Bridge events so we can observe completion and shut down automatically.
|
||||||
let (tx_bridge, rx_bridge) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
let (tx_bridge, rx_bridge) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
||||||
@@ -222,7 +231,6 @@ pub(crate) async fn run_codex_thread_one_shot(
|
|||||||
.send(Submission {
|
.send(Submission {
|
||||||
id: "shutdown".to_string(),
|
id: "shutdown".to_string(),
|
||||||
op: Op::Shutdown {},
|
op: Op::Shutdown {},
|
||||||
client_user_message_id: None,
|
|
||||||
trace: None,
|
trace: None,
|
||||||
parent_turn_id: None,
|
parent_turn_id: None,
|
||||||
root_turn_id: None,
|
root_turn_id: None,
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ async fn forward_ops_preserves_submission_trace_context() {
|
|||||||
let submission = Submission {
|
let submission = Submission {
|
||||||
id: "sub-1".to_string(),
|
id: "sub-1".to_string(),
|
||||||
op: Op::Interrupt,
|
op: Op::Interrupt,
|
||||||
client_user_message_id: None,
|
|
||||||
trace: Some(codex_protocol::protocol::W3cTraceContext {
|
trace: Some(codex_protocol::protocol::W3cTraceContext {
|
||||||
traceparent: Some(
|
traceparent: Some(
|
||||||
"00-1234567890abcdef1234567890abcdef-1234567890abcdef-01".to_string(),
|
"00-1234567890abcdef1234567890abcdef-1234567890abcdef-01".to_string(),
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ use crate::elicitation::ElicitationRegistration;
|
|||||||
use crate::environment_config::EnvironmentConfig;
|
use crate::environment_config::EnvironmentConfig;
|
||||||
use crate::session::SessionIo;
|
use crate::session::SessionIo;
|
||||||
use crate::session::SessionSettingsUpdate;
|
use crate::session::SessionSettingsUpdate;
|
||||||
use crate::session::SteerInputError;
|
|
||||||
use crate::session::TurnInput;
|
|
||||||
use crate::session::session::Session;
|
use crate::session::session::Session;
|
||||||
use crate::user_message_admission::UserMessageAdmission;
|
|
||||||
use codex_diagnostics::Gauge;
|
use codex_diagnostics::Gauge;
|
||||||
use codex_diagnostics::GaugeGuard;
|
use codex_diagnostics::GaugeGuard;
|
||||||
use codex_exec_server::SelectedCapabilityRootsStatus;
|
use codex_exec_server::SelectedCapabilityRootsStatus;
|
||||||
@@ -29,7 +26,6 @@ use codex_protocol::models::ContentItem;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use codex_protocol::openai_models::ReasoningEffort;
|
use codex_protocol::openai_models::ReasoningEffort;
|
||||||
use codex_protocol::protocol::AdditionalContextEntry;
|
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::Event;
|
use codex_protocol::protocol::Event;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
@@ -37,7 +33,6 @@ use codex_protocol::protocol::Op;
|
|||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::Submission;
|
|
||||||
use codex_protocol::protocol::ThreadHistoryMode;
|
use codex_protocol::protocol::ThreadHistoryMode;
|
||||||
use codex_protocol::protocol::ThreadMemoryMode;
|
use codex_protocol::protocol::ThreadMemoryMode;
|
||||||
use codex_protocol::protocol::ThreadSettingsSnapshot;
|
use codex_protocol::protocol::ThreadSettingsSnapshot;
|
||||||
@@ -46,7 +41,11 @@ use codex_protocol::protocol::TokenUsageInfo;
|
|||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
use codex_protocol::protocol::W3cTraceContext;
|
use codex_protocol::protocol::W3cTraceContext;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::turn_input::StartIfIdleSubmission;
|
||||||
|
use codex_protocol::turn_input::SteerSubmission;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
use codex_thread_store::PersistContext;
|
use codex_thread_store::PersistContext;
|
||||||
use codex_thread_store::StoredThread;
|
use codex_thread_store::StoredThread;
|
||||||
use codex_thread_store::StoredThreadHistory;
|
use codex_thread_store::StoredThreadHistory;
|
||||||
@@ -57,8 +56,6 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||||||
use codex_utils_path_uri::LegacyAppPathString;
|
use codex_utils_path_uri::LegacyAppPathString;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
use rmcp::model::ReadResourceRequestParams;
|
use rmcp::model::ReadResourceRequestParams;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -94,46 +91,6 @@ pub struct ThreadConfigSnapshot {
|
|||||||
pub originator: String,
|
pub originator: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explains why `CodexThread::try_start_turn_if_idle` rejected an automatic
|
|
||||||
/// idle turn.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum TryStartTurnIfIdleRejectionReason {
|
|
||||||
/// User/client-triggered mailbox work is already queued and must take
|
|
||||||
/// priority over extension-initiated idle work.
|
|
||||||
PendingTriggerTurn,
|
|
||||||
/// The thread is in Plan mode, where idle work without user input must not
|
|
||||||
/// start a new model turn.
|
|
||||||
PlanMode,
|
|
||||||
/// Another turn or task is active, or the idle reservation was lost before
|
|
||||||
/// the automatic turn could start.
|
|
||||||
Busy,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rejection returned when an extension asks to start automatic idle work but
|
|
||||||
/// the thread is not eligible to run it.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TryStartTurnIfIdleError {
|
|
||||||
reason: TryStartTurnIfIdleRejectionReason,
|
|
||||||
input: Vec<TurnInput>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryStartTurnIfIdleError {
|
|
||||||
pub(crate) fn new(reason: TryStartTurnIfIdleRejectionReason, input: Vec<TurnInput>) -> Self {
|
|
||||||
Self { reason, input }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the stable reason the automatic idle turn was rejected.
|
|
||||||
pub fn reason(&self) -> TryStartTurnIfIdleRejectionReason {
|
|
||||||
self.reason
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes the rejection and returns the original turn input
|
|
||||||
/// unchanged, so callers can retry, drop, or log it explicitly.
|
|
||||||
pub fn into_input(self) -> Vec<TurnInput> {
|
|
||||||
self.input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadConfigSnapshot {
|
impl ThreadConfigSnapshot {
|
||||||
pub fn cwd(&self) -> &AbsolutePathBuf {
|
pub fn cwd(&self) -> &AbsolutePathBuf {
|
||||||
&self.environments.legacy_fallback_cwd
|
&self.environments.legacy_fallback_cwd
|
||||||
@@ -300,88 +257,83 @@ impl CodexThread {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn submit_user_input_with_client_user_message_id(
|
/// Submits turn input without requiring the caller to inspect thread state.
|
||||||
|
///
|
||||||
|
/// The result describes whether Core started a turn, steered an active
|
||||||
|
/// turn, or declined it without recording or enqueueing the input. Only
|
||||||
|
/// user input is accepted.
|
||||||
|
pub async fn start_or_steer_turn(
|
||||||
&self,
|
&self,
|
||||||
op: Op,
|
request: TurnInputRequest,
|
||||||
trace: Option<W3cTraceContext>,
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
client_user_message_id: Option<String>,
|
self.submit_turn_input_with_mode(request, TurnInputMode::StartOrSteer)
|
||||||
) -> CodexResult<String> {
|
|
||||||
self.session
|
|
||||||
.services
|
|
||||||
.agent_control
|
|
||||||
.ensure_execution_capacity_for_op(self.session.thread_id(), &op)
|
|
||||||
.await?;
|
|
||||||
self.io
|
|
||||||
.submit_user_input_with_client_user_message_id(op, trace, client_user_message_id)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Waits until Core has started a turn or steered the active turn.
|
/// Starts a regular turn only when the thread is idle.
|
||||||
pub async fn submit_user_input_and_wait_for_admission(
|
///
|
||||||
|
/// Core declines the input without recording or enqueueing it when idle
|
||||||
|
/// work cannot start.
|
||||||
|
pub async fn start_turn_if_idle(
|
||||||
&self,
|
&self,
|
||||||
op: Op,
|
request: TurnInputRequest,
|
||||||
trace: Option<W3cTraceContext>,
|
) -> CodexResult<StartIfIdleSubmission> {
|
||||||
client_user_message_id: Option<String>,
|
match self
|
||||||
) -> CodexResult<UserMessageAdmission> {
|
.submit_turn_input_with_mode(request, TurnInputMode::StartIfIdle)
|
||||||
let Op::UserInput { .. } = &op else {
|
.await?
|
||||||
return Err(CodexErr::InvalidRequest(
|
{
|
||||||
"user message admission requires user input".to_string(),
|
TurnInputSubmission::Started { turn_id } => {
|
||||||
));
|
Ok(StartIfIdleSubmission::Started { turn_id })
|
||||||
};
|
}
|
||||||
self.session
|
TurnInputSubmission::NotSubmitted { reason } => {
|
||||||
.services
|
Ok(StartIfIdleSubmission::NotSubmitted { reason })
|
||||||
.agent_control
|
}
|
||||||
.ensure_execution_capacity_for_op(self.session.thread_id(), &op)
|
TurnInputSubmission::Steered { .. } => {
|
||||||
.await?;
|
unreachable!("start-if-idle submission cannot steer")
|
||||||
let submission_id = crate::session::new_submission_id();
|
}
|
||||||
let (_pending_admission, admission) = self
|
|
||||||
.session
|
|
||||||
.pending_user_message_admissions
|
|
||||||
.register(submission_id.clone());
|
|
||||||
self.io
|
|
||||||
.submit_with_id(Submission {
|
|
||||||
id: submission_id.clone(),
|
|
||||||
op,
|
|
||||||
client_user_message_id,
|
|
||||||
trace,
|
|
||||||
parent_turn_id: None,
|
|
||||||
root_turn_id: None,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
tokio::select! {
|
|
||||||
biased;
|
|
||||||
result = admission => result.unwrap_or(Err(CodexErr::InternalAgentDied)),
|
|
||||||
() = self.io.session_loop_termination.clone() => {
|
|
||||||
Err(CodexErr::InternalAgentDied)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Steers only if `expected_turn_id` is still the active regular turn.
|
||||||
|
pub async fn steer_turn(
|
||||||
|
&self,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
expected_turn_id: String,
|
||||||
|
) -> CodexResult<SteerSubmission> {
|
||||||
|
match self
|
||||||
|
.submit_turn_input_with_mode(request, TurnInputMode::Steer { expected_turn_id })
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
TurnInputSubmission::Steered { turn_id } => Ok(SteerSubmission::Steered { turn_id }),
|
||||||
|
TurnInputSubmission::NotSubmitted { reason } => {
|
||||||
|
Ok(SteerSubmission::NotSubmitted { reason })
|
||||||
|
}
|
||||||
|
TurnInputSubmission::Started { .. } => {
|
||||||
|
unreachable!("steer-only submission cannot start a turn")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn submit_turn_input_with_mode(
|
||||||
|
&self,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
mode: TurnInputMode,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
if !matches!(mode, TurnInputMode::Steer { .. }) {
|
||||||
|
self.session
|
||||||
|
.services
|
||||||
|
.agent_control
|
||||||
|
.ensure_execution_capacity_for_turn_start(self)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
self.io.submit_turn_input(request, mode).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Persist whether this thread is eligible for future memory generation.
|
/// Persist whether this thread is eligible for future memory generation.
|
||||||
pub async fn set_thread_memory_mode(&self, mode: ThreadMemoryMode) -> anyhow::Result<()> {
|
pub async fn set_thread_memory_mode(&self, mode: ThreadMemoryMode) -> anyhow::Result<()> {
|
||||||
self.session.set_thread_memory_mode(mode).await
|
self.session.set_thread_memory_mode(mode).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn steer_input(
|
|
||||||
&self,
|
|
||||||
input: Vec<UserInput>,
|
|
||||||
additional_context: BTreeMap<String, AdditionalContextEntry>,
|
|
||||||
expected_turn_id: Option<&str>,
|
|
||||||
client_user_message_id: Option<String>,
|
|
||||||
responsesapi_client_metadata: Option<HashMap<String, String>>,
|
|
||||||
) -> Result<String, SteerInputError> {
|
|
||||||
self.session
|
|
||||||
.steer_input(
|
|
||||||
input,
|
|
||||||
additional_context,
|
|
||||||
expected_turn_id,
|
|
||||||
client_user_message_id,
|
|
||||||
responsesapi_client_metadata,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects model-visible items into the currently active turn.
|
/// Injects model-visible items into the currently active turn.
|
||||||
///
|
///
|
||||||
/// This is the thread-level bridge to `Session::inject_if_running` for
|
/// This is the thread-level bridge to `Session::inject_if_running` for
|
||||||
@@ -394,26 +346,6 @@ impl CodexThread {
|
|||||||
self.session.inject_if_running(items).await
|
self.session.inject_if_running(items).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts an automatic regular turn with response items or user input only
|
|
||||||
/// when idle work is allowed for this thread.
|
|
||||||
///
|
|
||||||
/// This is the required entry point for extensions that want to launch
|
|
||||||
/// model-visible work from `ThreadLifecycleContributor::on_thread_idle`.
|
|
||||||
/// The call succeeds only if no user/client-triggered turn is queued and no
|
|
||||||
/// task is currently active. Work without user input is also rejected in
|
|
||||||
/// Plan mode. Active Review tasks are rejected by the active-task check
|
|
||||||
/// because Review turns are not steerable.
|
|
||||||
///
|
|
||||||
/// On rejection, the returned error includes a stable reason and carries
|
|
||||||
/// the original `items` unchanged so the caller can decide whether to drop
|
|
||||||
/// them, retry later, or log why no automatic turn was started.
|
|
||||||
pub async fn try_start_turn_if_idle(
|
|
||||||
&self,
|
|
||||||
items: Vec<TurnInput>,
|
|
||||||
) -> Result<(), TryStartTurnIfIdleError> {
|
|
||||||
self.session.try_start_turn_if_idle(items).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_app_server_client_info(
|
pub async fn set_app_server_client_info(
|
||||||
&self,
|
&self,
|
||||||
app_server_client_name: Option<String>,
|
app_server_client_name: Option<String>,
|
||||||
@@ -484,13 +416,6 @@ impl CodexThread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use sparingly: this is intended to be removed soon.
|
|
||||||
pub async fn submit_with_id(&self, mut sub: Submission) -> CodexResult<()> {
|
|
||||||
sub.parent_turn_id = None;
|
|
||||||
sub.root_turn_id = None;
|
|
||||||
self.io.submit_with_id(sub).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn next_event(&self) -> CodexResult<Event> {
|
pub async fn next_event(&self) -> CodexResult<Event> {
|
||||||
self.io.next_event().await
|
self.io.next_event().await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ use crate::session::turn_context::TurnContext;
|
|||||||
use codex_config::types::McpServerConfig;
|
use codex_config::types::McpServerConfig;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_model_provider_info::ModelProviderInfo;
|
use codex_model_provider_info::ModelProviderInfo;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
|
use codex_protocol::turn_input::TurnStartOptions;
|
||||||
use codex_thread_store::PersistContext;
|
use codex_thread_store::PersistContext;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
|
|
||||||
@@ -888,13 +892,9 @@ async fn run_review_on_session(
|
|||||||
.unwrap_or_else(|| params.parent_context.turn().config.cwd.clone());
|
.unwrap_or_else(|| params.parent_context.turn().config.cwd.clone());
|
||||||
|
|
||||||
let parent_turn = params.parent_context.turn();
|
let parent_turn = params.parent_context.turn();
|
||||||
let submission = review_session.io.submit_with_trace(
|
let submission = review_session.io.submit_turn_input(
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(prompt_items.items)
|
||||||
items: prompt_items.items,
|
.with_thread_settings(codex_protocol::protocol::ThreadSettingsOverrides {
|
||||||
final_output_json_schema: Some(params.schema.clone()),
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
||||||
parent_turn_legacy_fallback_cwd,
|
parent_turn_legacy_fallback_cwd,
|
||||||
parent_turn_environments,
|
parent_turn_environments,
|
||||||
@@ -913,11 +913,13 @@ async fn run_review_on_session(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
})
|
||||||
},
|
.on_start(TurnStartOptions {
|
||||||
/*trace*/ None,
|
final_output_json_schema: Some(params.schema.clone()),
|
||||||
Some(parent_turn.sub_id.clone()),
|
parent_turn_id: Some(parent_turn.sub_id.clone()),
|
||||||
parent_turn.turn_metadata_state.root_turn_id(),
|
root_turn_id: parent_turn.turn_metadata_state.root_turn_id(),
|
||||||
|
}),
|
||||||
|
TurnInputMode::StartIfIdle,
|
||||||
);
|
);
|
||||||
let submit_result = run_before_review_deadline(
|
let submit_result = run_before_review_deadline(
|
||||||
deadline,
|
deadline,
|
||||||
@@ -926,7 +928,17 @@ async fn run_review_on_session(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let child_turn_id = match submit_result {
|
let child_turn_id = match submit_result {
|
||||||
Ok(Ok(child_turn_id)) => child_turn_id,
|
Ok(Ok(TurnInputSubmission::Started { turn_id })) => turn_id,
|
||||||
|
Ok(Ok(submission)) => {
|
||||||
|
return (
|
||||||
|
GuardianReviewSessionOutcome::SessionFailed {
|
||||||
|
error: anyhow!("guardian review input was not started: {submission:?}"),
|
||||||
|
error_info: None,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
analytics_result,
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
return (
|
return (
|
||||||
GuardianReviewSessionOutcome::SessionFailed {
|
GuardianReviewSessionOutcome::SessionFailed {
|
||||||
@@ -1879,16 +1891,21 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
let submission = rx_sub.recv().await.expect("guardian submission");
|
let submission = rx_sub.recv().await.expect("guardian submission");
|
||||||
|
let id = submission.id;
|
||||||
|
let Op::TurnInput { reply, .. } = submission.op else {
|
||||||
|
panic!("expected turn-input submission");
|
||||||
|
};
|
||||||
|
reply
|
||||||
|
.send(Ok(TurnInputSubmission::Started {
|
||||||
|
turn_id: id.clone(),
|
||||||
|
}))
|
||||||
|
.expect("reply to guardian submission");
|
||||||
tx_event
|
tx_event
|
||||||
.send(turn_complete_event("prior-turn", Some("stale"), Some(9)))
|
.send(turn_complete_event("prior-turn", Some("stale"), Some(9)))
|
||||||
.await
|
.await
|
||||||
.expect("queue prior turn completion");
|
.expect("queue prior turn completion");
|
||||||
tx_event
|
tx_event
|
||||||
.send(turn_complete_event(
|
.send(turn_complete_event(id.as_str(), Some("fresh"), Some(42)))
|
||||||
submission.id.as_str(),
|
|
||||||
Some("fresh"),
|
|
||||||
Some(42),
|
|
||||||
))
|
|
||||||
.await
|
.await
|
||||||
.expect("queue submitted turn completion");
|
.expect("queue submitted turn completion");
|
||||||
|
|
||||||
@@ -1904,7 +1921,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn run_review_removes_trunk_when_event_stream_is_broken() {
|
async fn run_review_removes_trunk_when_event_stream_is_broken() {
|
||||||
let (mut review_session, tx_event, _rx_sub) = test_review_session().await;
|
let (mut review_session, tx_event, rx_sub) = test_review_session().await;
|
||||||
let params = test_review_params().await;
|
let params = test_review_params().await;
|
||||||
review_session.reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(
|
review_session.reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(
|
||||||
¶ms.spawn_config,
|
¶ms.spawn_config,
|
||||||
@@ -1915,16 +1932,26 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.history_version(),
|
.history_version(),
|
||||||
);
|
);
|
||||||
let manager = GuardianReviewSessionManager {
|
let manager = Arc::new(GuardianReviewSessionManager {
|
||||||
state: Arc::new(Mutex::new(GuardianReviewSessionState {
|
state: Arc::new(Mutex::new(GuardianReviewSessionState {
|
||||||
trunk: Some(Arc::new(review_session)),
|
trunk: Some(Arc::new(review_session)),
|
||||||
ephemeral_reviews: Vec::new(),
|
ephemeral_reviews: Vec::new(),
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
});
|
||||||
|
let manager_for_review = Arc::clone(&manager);
|
||||||
|
let review = tokio::spawn(async move { manager_for_review.run_review(params).await });
|
||||||
|
let submission = rx_sub.recv().await.expect("guardian submission");
|
||||||
|
let id = submission.id;
|
||||||
|
let Op::TurnInput { reply, .. } = submission.op else {
|
||||||
|
panic!("expected turn-input submission");
|
||||||
};
|
};
|
||||||
|
reply
|
||||||
|
.send(Ok(TurnInputSubmission::Started { turn_id: id }))
|
||||||
|
.expect("reply to guardian submission");
|
||||||
drop(tx_event);
|
drop(tx_event);
|
||||||
|
|
||||||
let (outcome, _) = manager.run_review(params).await;
|
let (outcome, _) = review.await.expect("review task should complete");
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
outcome,
|
outcome,
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ mod realtime_prompt;
|
|||||||
mod responses_metadata;
|
mod responses_metadata;
|
||||||
mod responses_retry;
|
mod responses_retry;
|
||||||
pub(crate) mod session;
|
pub(crate) mod session;
|
||||||
|
pub use codex_protocol::turn_input::NotSubmittedReason;
|
||||||
|
pub use codex_protocol::turn_input::StartIfIdleSubmission;
|
||||||
|
pub use codex_protocol::turn_input::SteerSubmission;
|
||||||
|
pub use codex_protocol::turn_input::TurnInput;
|
||||||
|
pub use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
pub use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
|
pub use codex_protocol::turn_input::TurnStartOptions;
|
||||||
pub use responses_metadata::CodexResponsesMetadata;
|
pub use responses_metadata::CodexResponsesMetadata;
|
||||||
pub use session::SteerInputError;
|
|
||||||
pub use session::TurnInput;
|
|
||||||
pub use turn_metadata::detached_memory_responses_metadata;
|
pub use turn_metadata::detached_memory_responses_metadata;
|
||||||
mod codex_thread;
|
mod codex_thread;
|
||||||
mod compact_model_fallback;
|
mod compact_model_fallback;
|
||||||
@@ -25,16 +30,12 @@ mod compact_remote;
|
|||||||
mod compact_remote_history;
|
mod compact_remote_history;
|
||||||
mod compact_remote_v2;
|
mod compact_remote_v2;
|
||||||
mod compact_token_budget;
|
mod compact_token_budget;
|
||||||
mod user_message_admission;
|
|
||||||
pub use codex_thread::BackgroundTerminalInfo;
|
pub use codex_thread::BackgroundTerminalInfo;
|
||||||
pub use codex_thread::CodexThread;
|
pub use codex_thread::CodexThread;
|
||||||
pub use codex_thread::CodexThreadSettingsOverrides;
|
pub use codex_thread::CodexThreadSettingsOverrides;
|
||||||
pub use codex_thread::ThreadConfigSnapshot;
|
pub use codex_thread::ThreadConfigSnapshot;
|
||||||
pub use codex_thread::TryStartTurnIfIdleError;
|
|
||||||
pub use codex_thread::TryStartTurnIfIdleRejectionReason;
|
|
||||||
pub use environment_config::EnvironmentConfig;
|
pub use environment_config::EnvironmentConfig;
|
||||||
pub use session::turn_context::TurnContext;
|
pub use session::turn_context::TurnContext;
|
||||||
pub use user_message_admission::UserMessageAdmission;
|
|
||||||
mod agent;
|
mod agent;
|
||||||
mod agent_communication;
|
mod agent_communication;
|
||||||
mod attestation;
|
mod attestation;
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ use tracing::Instrument;
|
|||||||
use tracing::debug_span;
|
use tracing::debug_span;
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
|
|
||||||
use crate::session::SteerInputError;
|
|
||||||
use crate::session::TurnInput;
|
|
||||||
use crate::session::session::Session;
|
use crate::session::session::Session;
|
||||||
use crate::session::session::SessionSettingsUpdate;
|
use crate::session::thread_settings;
|
||||||
|
use crate::session::turn_input;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::review_prompts::resolve_review_request;
|
use crate::review_prompts::resolve_review_request;
|
||||||
@@ -22,10 +21,7 @@ use crate::tasks::CompactTask;
|
|||||||
use crate::tasks::UserShellCommandMode;
|
use crate::tasks::UserShellCommandMode;
|
||||||
use crate::tasks::UserShellCommandTask;
|
use crate::tasks::UserShellCommandTask;
|
||||||
use crate::tasks::execute_user_shell_command;
|
use crate::tasks::execute_user_shell_command;
|
||||||
use crate::user_message_admission::UserMessageAdmission;
|
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
use codex_protocol::error::CodexErr;
|
|
||||||
use codex_protocol::error::Result as CodexResult;
|
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::ResponseInputItem;
|
use codex_protocol::models::ResponseInputItem;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
@@ -43,8 +39,6 @@ use codex_protocol::protocol::ReviewDecision;
|
|||||||
use codex_protocol::protocol::ReviewRequest;
|
use codex_protocol::protocol::ReviewRequest;
|
||||||
use codex_protocol::protocol::ThreadMemoryMode;
|
use codex_protocol::protocol::ThreadMemoryMode;
|
||||||
use codex_protocol::protocol::ThreadRolledBackEvent;
|
use codex_protocol::protocol::ThreadRolledBackEvent;
|
||||||
use codex_protocol::protocol::ThreadSettingsAppliedEvent;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
|
||||||
use codex_protocol::protocol::TurnAbortReason;
|
use codex_protocol::protocol::TurnAbortReason;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -82,231 +76,6 @@ pub async fn realtime_conversation_list_voices(sess: &Session, sub_id: String) {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_input_or_turn(
|
|
||||||
sess: &Arc<Session>,
|
|
||||||
sub_id: String,
|
|
||||||
op: Op,
|
|
||||||
client_user_message_id: Option<String>,
|
|
||||||
parent_turn_id: Option<String>,
|
|
||||||
root_turn_id: Option<String>,
|
|
||||||
) {
|
|
||||||
let admission = user_input_or_turn_inner(
|
|
||||||
sess,
|
|
||||||
sub_id.clone(),
|
|
||||||
op,
|
|
||||||
client_user_message_id,
|
|
||||||
parent_turn_id,
|
|
||||||
root_turn_id,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
sess.pending_user_message_admissions
|
|
||||||
.complete(&sub_id, admission);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_thread_settings(
|
|
||||||
sess: &Arc<Session>,
|
|
||||||
sub_id: String,
|
|
||||||
thread_settings: ThreadSettingsOverrides,
|
|
||||||
) {
|
|
||||||
let updates = thread_settings_update(sess, thread_settings).await;
|
|
||||||
match sess.update_settings(updates).await {
|
|
||||||
Ok(()) => {
|
|
||||||
sess.send_event_raw_without_materializing_rollout(Event {
|
|
||||||
id: sub_id,
|
|
||||||
msg: thread_settings_applied_event(sess).await,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
sess.send_event_raw(Event {
|
|
||||||
id: sub_id,
|
|
||||||
msg: EventMsg::Error(ErrorEvent {
|
|
||||||
message: format!("invalid thread settings override: {err}"),
|
|
||||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn thread_settings_update(
|
|
||||||
sess: &Session,
|
|
||||||
thread_settings: ThreadSettingsOverrides,
|
|
||||||
) -> SessionSettingsUpdate {
|
|
||||||
let ThreadSettingsOverrides {
|
|
||||||
environments,
|
|
||||||
profile_workspace_roots,
|
|
||||||
approval_policy,
|
|
||||||
approvals_reviewer,
|
|
||||||
sandbox_policy,
|
|
||||||
permission_profile,
|
|
||||||
active_permission_profile,
|
|
||||||
windows_sandbox_level,
|
|
||||||
model,
|
|
||||||
effort,
|
|
||||||
summary,
|
|
||||||
service_tier,
|
|
||||||
collaboration_mode,
|
|
||||||
personality,
|
|
||||||
} = thread_settings;
|
|
||||||
let collaboration_mode = match collaboration_mode {
|
|
||||||
Some(collaboration_mode) => collaboration_mode,
|
|
||||||
None => {
|
|
||||||
let state = sess.state.lock().await;
|
|
||||||
// Model and reasoning effort live in CollaborationMode settings today, so
|
|
||||||
// partial thread-settings updates refresh those fields on the active mode.
|
|
||||||
state
|
|
||||||
.session_configuration
|
|
||||||
.collaboration_mode
|
|
||||||
.with_updates(model, effort, /*developer_instructions*/ None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
SessionSettingsUpdate {
|
|
||||||
environments,
|
|
||||||
profile_workspace_roots,
|
|
||||||
approval_policy,
|
|
||||||
approvals_reviewer,
|
|
||||||
sandbox_policy,
|
|
||||||
permission_profile,
|
|
||||||
active_permission_profile,
|
|
||||||
windows_sandbox_level,
|
|
||||||
collaboration_mode: Some(collaboration_mode),
|
|
||||||
reasoning_summary: summary,
|
|
||||||
service_tier,
|
|
||||||
personality,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn thread_settings_applied_event(sess: &Session) -> EventMsg {
|
|
||||||
let snapshot = {
|
|
||||||
let state = sess.state.lock().await;
|
|
||||||
state.session_configuration.thread_config_snapshot()
|
|
||||||
};
|
|
||||||
EventMsg::ThreadSettingsApplied(ThreadSettingsAppliedEvent {
|
|
||||||
thread_settings: snapshot.into_thread_settings_snapshot(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn user_input_or_turn_inner(
|
|
||||||
sess: &Arc<Session>,
|
|
||||||
sub_id: String,
|
|
||||||
op: Op,
|
|
||||||
client_user_message_id: Option<String>,
|
|
||||||
parent_turn_id: Option<String>,
|
|
||||||
root_turn_id: Option<String>,
|
|
||||||
) -> CodexResult<UserMessageAdmission> {
|
|
||||||
let Op::UserInput {
|
|
||||||
items,
|
|
||||||
final_output_json_schema,
|
|
||||||
responsesapi_client_metadata,
|
|
||||||
additional_context,
|
|
||||||
thread_settings,
|
|
||||||
} = op
|
|
||||||
else {
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
let emit_thread_settings_applied = thread_settings != ThreadSettingsOverrides::default();
|
|
||||||
let mut updates = if emit_thread_settings_applied {
|
|
||||||
thread_settings_update(sess, thread_settings).await
|
|
||||||
} else {
|
|
||||||
SessionSettingsUpdate::default()
|
|
||||||
};
|
|
||||||
updates.final_output_json_schema = Some(final_output_json_schema);
|
|
||||||
|
|
||||||
// new_turn_with_sub_id already emits an error event when settings are invalid.
|
|
||||||
let current_context = sess.new_turn_with_sub_id(sub_id.clone(), updates).await?;
|
|
||||||
if emit_thread_settings_applied {
|
|
||||||
sess.send_event_raw_without_materializing_rollout(Event {
|
|
||||||
id: sub_id.clone(),
|
|
||||||
msg: thread_settings_applied_event(sess).await,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
sess.maybe_emit_model_warnings_for_turn(current_context.as_ref())
|
|
||||||
.await;
|
|
||||||
if let Some(id) = root_turn_id.as_ref() {
|
|
||||||
current_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id(id.clone());
|
|
||||||
}
|
|
||||||
let incoming_turn_metadata = parent_turn_id
|
|
||||||
.as_ref()
|
|
||||||
.map(|_| current_context.turn_metadata_state.as_ref());
|
|
||||||
match sess
|
|
||||||
.steer_input(
|
|
||||||
items.clone(),
|
|
||||||
additional_context.clone(),
|
|
||||||
/*expected_turn_id*/ None,
|
|
||||||
client_user_message_id.clone(),
|
|
||||||
responsesapi_client_metadata.clone(),
|
|
||||||
incoming_turn_metadata,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(turn_id) => {
|
|
||||||
current_context.session_telemetry.user_prompt(&items);
|
|
||||||
Ok(UserMessageAdmission::Steered { turn_id })
|
|
||||||
}
|
|
||||||
Err(SteerInputError::NoActiveTurn(items)) => {
|
|
||||||
if root_turn_id.is_none()
|
|
||||||
&& parent_turn_id.is_none()
|
|
||||||
&& !items.is_empty()
|
|
||||||
&& current_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.can_start_root_turn(¤t_context.session_source)
|
|
||||||
{
|
|
||||||
current_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id(sub_id.clone());
|
|
||||||
}
|
|
||||||
if let Some(id) = parent_turn_id {
|
|
||||||
current_context.turn_metadata_state.set_parent_turn_id(id);
|
|
||||||
}
|
|
||||||
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
|
|
||||||
current_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_responsesapi_client_metadata(responsesapi_client_metadata);
|
|
||||||
}
|
|
||||||
current_context.session_telemetry.user_prompt(&items);
|
|
||||||
let additional_context_input = {
|
|
||||||
let mut state = sess.state.lock().await;
|
|
||||||
state.additional_context.merge(additional_context)
|
|
||||||
};
|
|
||||||
let mut task_input = additional_context_input
|
|
||||||
.into_iter()
|
|
||||||
.map(ResponseItem::from)
|
|
||||||
.map(|item| sess.annotate_client_response_item(item))
|
|
||||||
.map(TurnInput::ResponseItem)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !items.is_empty() {
|
|
||||||
task_input.push(TurnInput::UserInput {
|
|
||||||
content: items,
|
|
||||||
client_id: client_user_message_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(¤t_context),
|
|
||||||
task_input,
|
|
||||||
crate::tasks::RegularTask::new(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(UserMessageAdmission::Started { turn_id: sub_id })
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
sess.send_event_raw(Event {
|
|
||||||
id: sub_id.clone(),
|
|
||||||
msg: EventMsg::Error(err.to_error_event()),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
Err(CodexErr::InvalidRequest(format!(
|
|
||||||
"failed to admit user message: {err:?}"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues an inter-agent message, then lets the shared pending-work scheduler
|
/// Queues an inter-agent message, then lets the shared pending-work scheduler
|
||||||
/// decide whether an idle session should start a regular turn.
|
/// decide whether an idle session should start a regular turn.
|
||||||
pub async fn inter_agent_communication(
|
pub async fn inter_agent_communication(
|
||||||
@@ -787,20 +556,17 @@ pub(super) async fn submission_loop(
|
|||||||
realtime_conversation_list_voices(&sess, sub.id.clone()).await;
|
realtime_conversation_list_voices(&sess, sub.id.clone()).await;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
op @ Op::UserInput { .. } => {
|
Op::TurnInput {
|
||||||
user_input_or_turn(
|
request,
|
||||||
&sess,
|
mode,
|
||||||
sub.id.clone(),
|
reply,
|
||||||
op,
|
} => {
|
||||||
sub.client_user_message_id,
|
let result = turn_input::handle(&sess, *request, mode, sub.id.clone()).await;
|
||||||
sub.parent_turn_id,
|
let _ = reply.send(result);
|
||||||
sub.root_turn_id,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Op::ThreadSettings { thread_settings } => {
|
Op::ThreadSettings { thread_settings } => {
|
||||||
update_thread_settings(&sess, sub.id.clone(), thread_settings).await;
|
thread_settings::update(&sess, sub.id.clone(), thread_settings).await;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Op::InterAgentCommunication { communication } => {
|
Op::InterAgentCommunication { communication } => {
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
use super::input_queue::TurnInput;
|
use super::TurnInput as PendingTurnInput;
|
||||||
use super::session::Session;
|
use super::session::Session;
|
||||||
use super::turn_context::TurnContext;
|
use super::turn_context::TurnContext;
|
||||||
use crate::codex_thread::TryStartTurnIfIdleError;
|
|
||||||
use crate::codex_thread::TryStartTurnIfIdleRejectionReason;
|
|
||||||
use crate::state::ActiveTurn;
|
|
||||||
use crate::state::TurnState;
|
|
||||||
use crate::tasks::MailboxParentProvenance;
|
|
||||||
use crate::tasks::RegularTask;
|
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_history::CodexHarnessMetadata;
|
use codex_history::CodexHarnessMetadata;
|
||||||
use codex_history::ResponseItemEnvelope;
|
use codex_history::ResponseItemEnvelope;
|
||||||
use codex_protocol::config_types::ModeKind;
|
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
/// Returns the input if there is no active turn to inject into.
|
/// Returns the input if there is no active turn to inject into.
|
||||||
@@ -33,7 +25,7 @@ impl Session {
|
|||||||
input
|
input
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(ResponseItemEnvelope::new)
|
.map(ResponseItemEnvelope::new)
|
||||||
.map(TurnInput::ResponseItem)
|
.map(PendingTurnInput::ResponseItem)
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -62,7 +54,10 @@ impl Session {
|
|||||||
self.input_queue
|
self.input_queue
|
||||||
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
|
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
|
||||||
active_turn.turn_state.as_ref(),
|
active_turn.turn_state.as_ref(),
|
||||||
items.into_iter().map(TurnInput::ResponseItem).collect(),
|
items
|
||||||
|
.into_iter()
|
||||||
|
.map(PendingTurnInput::ResponseItem)
|
||||||
|
.collect(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
@@ -119,136 +114,6 @@ impl Session {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a regular turn with the provided input only if automatic idle work
|
|
||||||
/// is allowed for the current session state.
|
|
||||||
///
|
|
||||||
/// This is the shared gate for extension-initiated idle work. It refuses to
|
|
||||||
/// start a turn when user/client-triggered work is queued or any task is
|
|
||||||
/// still active. Work without user input is also rejected in Plan mode.
|
|
||||||
/// Active Review tasks are covered by the active-task check because Review
|
|
||||||
/// turns are not steerable.
|
|
||||||
pub(crate) async fn try_start_turn_if_idle(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
input: Vec<TurnInput>,
|
|
||||||
) -> Result<(), TryStartTurnIfIdleError> {
|
|
||||||
if input.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let has_user_input = input.iter().any(
|
|
||||||
|item| matches!(item, TurnInput::UserInput { content, .. } if !content.is_empty()),
|
|
||||||
);
|
|
||||||
if self.input_queue.has_trigger_turn_mailbox_items().await {
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PendingTriggerTurn,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if !has_user_input && self.collaboration_mode().await.mode == ModeKind::Plan {
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PlanMode,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let turn_state = {
|
|
||||||
let mut active_turn = self.active_turn.lock().await;
|
|
||||||
if active_turn.is_some() {
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::Busy,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let active_turn = active_turn.get_or_insert_with(ActiveTurn::default);
|
|
||||||
Arc::clone(&active_turn.turn_state)
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.input_queue.has_trigger_turn_mailbox_items().await {
|
|
||||||
self.clear_reserved_idle_turn(&turn_state).await;
|
|
||||||
self.maybe_start_turn_for_pending_work().await;
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PendingTriggerTurn,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let turn_context = self
|
|
||||||
.new_default_turn_with_sub_id(uuid::Uuid::new_v4().to_string())
|
|
||||||
.await;
|
|
||||||
if !has_user_input && turn_context.mode == ModeKind::Plan {
|
|
||||||
self.clear_reserved_idle_turn(&turn_state).await;
|
|
||||||
self.maybe_start_turn_for_pending_work().await;
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PlanMode,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.maybe_emit_model_warnings_for_turn(turn_context.as_ref())
|
|
||||||
.await;
|
|
||||||
if self.input_queue.has_trigger_turn_mailbox_items().await {
|
|
||||||
self.clear_reserved_idle_turn(&turn_state).await;
|
|
||||||
self.maybe_start_turn_for_pending_work().await;
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PendingTriggerTurn,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let still_reserved = {
|
|
||||||
let active_turn = self.active_turn.lock().await;
|
|
||||||
active_turn.as_ref().is_some_and(|active_turn| {
|
|
||||||
active_turn.task.is_none() && Arc::ptr_eq(&active_turn.turn_state, &turn_state)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
if !still_reserved {
|
|
||||||
self.clear_reserved_idle_turn(&turn_state).await;
|
|
||||||
return Err(TryStartTurnIfIdleError::new(
|
|
||||||
TryStartTurnIfIdleRejectionReason::Busy,
|
|
||||||
input,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_user_input
|
|
||||||
&& turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.can_start_root_turn(&turn_context.session_source)
|
|
||||||
{
|
|
||||||
turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id(turn_context.sub_id.clone());
|
|
||||||
}
|
|
||||||
let task_input = if has_user_input {
|
|
||||||
self.clear_connector_selection().await;
|
|
||||||
for item in &input {
|
|
||||||
if let TurnInput::UserInput { content, .. } = item {
|
|
||||||
turn_context.session_telemetry.user_prompt(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input
|
|
||||||
} else {
|
|
||||||
self.input_queue
|
|
||||||
.extend_pending_input_for_turn_state(turn_state.as_ref(), input)
|
|
||||||
.await;
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
self.start_task(
|
|
||||||
turn_context,
|
|
||||||
task_input,
|
|
||||||
RegularTask::new(),
|
|
||||||
MailboxParentProvenance::Ignore,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn clear_reserved_idle_turn(&self, turn_state: &Arc<tokio::sync::Mutex<TurnState>>) {
|
|
||||||
let mut active_turn_guard = self.active_turn.lock().await;
|
|
||||||
if let Some(active_turn) = active_turn_guard.as_ref()
|
|
||||||
&& active_turn.task.is_none()
|
|
||||||
&& Arc::ptr_eq(&active_turn.turn_state, turn_state)
|
|
||||||
{
|
|
||||||
*active_turn_guard = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects items into active work, or records them without starting a turn.
|
/// Injects items into active work, or records them without starting a turn.
|
||||||
pub(crate) async fn inject_no_new_turn(
|
pub(crate) async fn inject_no_new_turn(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use codex_history::CodexHarnessMetadata;
|
use codex_history::CodexHarnessMetadata;
|
||||||
use codex_protocol::AgentPath;
|
use codex_protocol::AgentPath;
|
||||||
|
use codex_protocol::user_input::UserInput;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -92,9 +91,7 @@ use codex_protocol::approvals::NetworkPolicyAmendment;
|
|||||||
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
||||||
use codex_protocol::config_types::ModeKind;
|
|
||||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::Settings;
|
|
||||||
use codex_protocol::config_types::WebSearchMode;
|
use codex_protocol::config_types::WebSearchMode;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||||
@@ -111,7 +108,6 @@ use codex_protocol::openai_models::ModelInfo;
|
|||||||
use codex_protocol::openai_models::ModelPreset;
|
use codex_protocol::openai_models::ModelPreset;
|
||||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AdditionalContextEntry;
|
|
||||||
use codex_protocol::protocol::FileChange;
|
use codex_protocol::protocol::FileChange;
|
||||||
use codex_protocol::protocol::HasLegacyEvent;
|
use codex_protocol::protocol::HasLegacyEvent;
|
||||||
use codex_protocol::protocol::HistoryPosition;
|
use codex_protocol::protocol::HistoryPosition;
|
||||||
@@ -221,17 +217,19 @@ mod rollout_reconstruction;
|
|||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
pub(crate) mod session;
|
pub(crate) mod session;
|
||||||
pub(crate) mod step_context;
|
pub(crate) mod step_context;
|
||||||
|
mod thread_settings;
|
||||||
pub(crate) mod time_reminder;
|
pub(crate) mod time_reminder;
|
||||||
mod token_budget;
|
mod token_budget;
|
||||||
pub(crate) mod turn;
|
pub(crate) mod turn;
|
||||||
pub(crate) mod turn_context;
|
pub(crate) mod turn_context;
|
||||||
|
mod turn_input;
|
||||||
mod world_state;
|
mod world_state;
|
||||||
use self::code_mode_warning::unsupported_code_mode_warning;
|
use self::code_mode_warning::unsupported_code_mode_warning;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::handlers::submission_dispatch_span;
|
use self::handlers::submission_dispatch_span;
|
||||||
use self::handlers::submission_loop;
|
use self::handlers::submission_loop;
|
||||||
pub(crate) use self::input_queue::InputQueueActivity;
|
pub(crate) use self::input_queue::InputQueueActivity;
|
||||||
pub use self::input_queue::TurnInput;
|
pub(crate) use self::input_queue::TurnInput;
|
||||||
pub(crate) use self::input_queue::TurnInputQueue;
|
pub(crate) use self::input_queue::TurnInputQueue;
|
||||||
use self::review::spawn_review_thread;
|
use self::review::spawn_review_thread;
|
||||||
use self::session::AppServerClientMetadata;
|
use self::session::AppServerClientMetadata;
|
||||||
@@ -248,45 +246,6 @@ use self::turn_context::TurnContext;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod rollout_reconstruction_tests;
|
mod rollout_reconstruction_tests;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum SteerInputError {
|
|
||||||
NoActiveTurn(Vec<UserInput>),
|
|
||||||
ExpectedTurnMismatch { expected: String, actual: String },
|
|
||||||
ActiveTurnNotSteerable { turn_kind: NonSteerableTurnKind },
|
|
||||||
EmptyInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SteerInputError {
|
|
||||||
fn to_error_event(&self) -> ErrorEvent {
|
|
||||||
match self {
|
|
||||||
Self::NoActiveTurn(_) => ErrorEvent {
|
|
||||||
message: "no active turn to steer".to_string(),
|
|
||||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
|
||||||
},
|
|
||||||
Self::ExpectedTurnMismatch { expected, actual } => ErrorEvent {
|
|
||||||
message: format!("expected active turn id `{expected}` but found `{actual}`"),
|
|
||||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
|
||||||
},
|
|
||||||
Self::ActiveTurnNotSteerable { turn_kind } => {
|
|
||||||
let turn_kind_label = match turn_kind {
|
|
||||||
NonSteerableTurnKind::Review => "review",
|
|
||||||
NonSteerableTurnKind::Compact => "compact",
|
|
||||||
};
|
|
||||||
ErrorEvent {
|
|
||||||
message: format!("cannot steer a {turn_kind_label} turn"),
|
|
||||||
codex_error_info: Some(CodexErrorInfo::ActiveTurnNotSteerable {
|
|
||||||
turn_kind: *turn_kind,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::EmptyInput => ErrorEvent {
|
|
||||||
message: "input must not be empty".to_string(),
|
|
||||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notes from the previous real user turn.
|
/// Notes from the previous real user turn.
|
||||||
///
|
///
|
||||||
/// Conceptually this is the same role that `previous_model` used to fill, but
|
/// Conceptually this is the same role that `previous_model` used to fill, but
|
||||||
@@ -344,8 +303,10 @@ use codex_otel::THREAD_STARTED_METRIC;
|
|||||||
use codex_otel::TelemetryAuthMode;
|
use codex_otel::TelemetryAuthMode;
|
||||||
use codex_protocol::ResponseItemId;
|
use codex_protocol::ResponseItemId;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||||
use codex_protocol::mcp::ClientMcpExtensions;
|
use codex_protocol::mcp::ClientMcpExtensions;
|
||||||
use codex_protocol::models::LocalImagePreparation;
|
use codex_protocol::models::LocalImagePreparation;
|
||||||
@@ -356,7 +317,6 @@ use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::CodexErrorInfo;
|
use codex_protocol::protocol::CodexErrorInfo;
|
||||||
use codex_protocol::protocol::DeprecationNoticeEvent;
|
use codex_protocol::protocol::DeprecationNoticeEvent;
|
||||||
use codex_protocol::protocol::ErrorEvent;
|
|
||||||
use codex_protocol::protocol::Event;
|
use codex_protocol::protocol::Event;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
||||||
@@ -365,7 +325,6 @@ use codex_protocol::protocol::ModelRerouteReason;
|
|||||||
use codex_protocol::protocol::ModelVerification;
|
use codex_protocol::protocol::ModelVerification;
|
||||||
use codex_protocol::protocol::ModelVerificationEvent;
|
use codex_protocol::protocol::ModelVerificationEvent;
|
||||||
use codex_protocol::protocol::NetworkApprovalContext;
|
use codex_protocol::protocol::NetworkApprovalContext;
|
||||||
use codex_protocol::protocol::NonSteerableTurnKind;
|
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::RateLimitSnapshot;
|
use codex_protocol::protocol::RateLimitSnapshot;
|
||||||
use codex_protocol::protocol::RequestUserInputEvent;
|
use codex_protocol::protocol::RequestUserInputEvent;
|
||||||
@@ -381,6 +340,9 @@ use codex_protocol::protocol::TokenUsage;
|
|||||||
use codex_protocol::protocol::TokenUsageInfo;
|
use codex_protocol::protocol::TokenUsageInfo;
|
||||||
use codex_protocol::protocol::TurnModerationMetadataEvent;
|
use codex_protocol::protocol::TurnModerationMetadataEvent;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_skills_extension::HostSkillsService;
|
use codex_skills_extension::HostSkillsService;
|
||||||
use codex_tools::UnifiedExecShellMode;
|
use codex_tools::UnifiedExecShellMode;
|
||||||
@@ -847,7 +809,6 @@ impl SessionIo {
|
|||||||
let sub = Submission {
|
let sub = Submission {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
op,
|
op,
|
||||||
client_user_message_id: None,
|
|
||||||
trace,
|
trace,
|
||||||
parent_turn_id,
|
parent_turn_id,
|
||||||
root_turn_id,
|
root_turn_id,
|
||||||
@@ -856,26 +817,6 @@ impl SessionIo {
|
|||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn submit_user_input_with_client_user_message_id(
|
|
||||||
&self,
|
|
||||||
op: Op,
|
|
||||||
trace: Option<W3cTraceContext>,
|
|
||||||
client_user_message_id: Option<String>,
|
|
||||||
) -> CodexResult<String> {
|
|
||||||
debug_assert!(matches!(op, Op::UserInput { .. }));
|
|
||||||
let id = new_submission_id();
|
|
||||||
let sub = Submission {
|
|
||||||
id: id.clone(),
|
|
||||||
op,
|
|
||||||
client_user_message_id,
|
|
||||||
trace,
|
|
||||||
parent_turn_id: None,
|
|
||||||
root_turn_id: None,
|
|
||||||
};
|
|
||||||
self.submit_with_id(sub).await?;
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use sparingly: prefer `submit()` so submission IDs are generated consistently.
|
/// Use sparingly: prefer `submit()` so submission IDs are generated consistently.
|
||||||
pub(crate) async fn submit_with_id(&self, mut sub: Submission) -> CodexResult<()> {
|
pub(crate) async fn submit_with_id(&self, mut sub: Submission) -> CodexResult<()> {
|
||||||
if sub.trace.is_none() {
|
if sub.trace.is_none() {
|
||||||
@@ -888,6 +829,33 @@ impl SessionIo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Submits an ordered turn-input call and waits only for Core's routing decision.
|
||||||
|
///
|
||||||
|
/// Once queued, dropping the waiter does not retract the call. If the
|
||||||
|
/// session loop exits before replying, the caller gets `InternalAgentDied`.
|
||||||
|
pub(crate) async fn submit_turn_input(
|
||||||
|
&self,
|
||||||
|
mut request: TurnInputRequest,
|
||||||
|
mode: TurnInputMode,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
let id = new_submission_id();
|
||||||
|
let (reply_tx, reply_rx) = oneshot::channel();
|
||||||
|
let trace = request.trace.take();
|
||||||
|
self.submit_with_id(Submission {
|
||||||
|
id,
|
||||||
|
op: Op::TurnInput {
|
||||||
|
request: Box::new(request),
|
||||||
|
mode,
|
||||||
|
reply: reply_tx,
|
||||||
|
},
|
||||||
|
trace,
|
||||||
|
parent_turn_id: None,
|
||||||
|
root_turn_id: None,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
reply_rx.await.unwrap_or(Err(CodexErr::InternalAgentDied))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn shutdown_and_wait(&self) -> CodexResult<()> {
|
pub(crate) async fn shutdown_and_wait(&self) -> CodexResult<()> {
|
||||||
let session_loop_termination = self.session_loop_termination.clone();
|
let session_loop_termination = self.session_loop_termination.clone();
|
||||||
match self.submit(Op::Shutdown).await {
|
match self.submit(Op::Shutdown).await {
|
||||||
@@ -1248,27 +1216,6 @@ impl Session {
|
|||||||
format!("auto-compact-{id}")
|
format!("auto-compact-{id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn route_realtime_text_input(self: &Arc<Self>, text: String) {
|
|
||||||
handlers::user_input_or_turn(
|
|
||||||
self,
|
|
||||||
Uuid::now_v7().to_string(),
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text,
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
},
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*parent_turn_id*/ None,
|
|
||||||
/*root_turn_id*/ None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_total_token_usage(&self) -> i64 {
|
pub(crate) async fn get_total_token_usage(&self) -> i64 {
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
state.get_total_token_usage(state.server_reasoning_included())
|
state.get_total_token_usage(state.server_reasoning_included())
|
||||||
@@ -1434,7 +1381,7 @@ impl Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let thread_settings_applied =
|
let thread_settings_applied =
|
||||||
RolloutItem::EventMsg(handlers::thread_settings_applied_event(self).await);
|
RolloutItem::EventMsg(thread_settings::applied_event(self).await);
|
||||||
match &self.fork_persistence {
|
match &self.fork_persistence {
|
||||||
ForkPersistence::Referenced {
|
ForkPersistence::Referenced {
|
||||||
inherited_item_count,
|
inherited_item_count,
|
||||||
@@ -4043,99 +3990,6 @@ impl Session {
|
|||||||
self.send_event(turn_context, event).await;
|
self.send_event(turn_context, event).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject additional user input into the currently active turn.
|
|
||||||
///
|
|
||||||
/// Returns the active turn id when accepted.
|
|
||||||
#[expect(
|
|
||||||
clippy::await_holding_invalid_type,
|
|
||||||
reason = "active turn checks and turn state updates must remain atomic"
|
|
||||||
)]
|
|
||||||
pub async fn steer_input(
|
|
||||||
&self,
|
|
||||||
input: Vec<UserInput>,
|
|
||||||
additional_context: BTreeMap<String, AdditionalContextEntry>,
|
|
||||||
expected_turn_id: Option<&str>,
|
|
||||||
client_user_message_id: Option<String>,
|
|
||||||
responsesapi_client_metadata: Option<HashMap<String, String>>,
|
|
||||||
incoming_turn_metadata: Option<&TurnMetadataState>,
|
|
||||||
) -> Result<String, SteerInputError> {
|
|
||||||
let mut active = self.active_turn.lock().await;
|
|
||||||
let Some(active_turn) = active.as_mut() else {
|
|
||||||
return Err(SteerInputError::NoActiveTurn(input));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(active_task) = active_turn.task.as_ref() else {
|
|
||||||
return Err(SteerInputError::NoActiveTurn(input));
|
|
||||||
};
|
|
||||||
let active_turn_id = &active_task.turn_context.sub_id;
|
|
||||||
|
|
||||||
if let Some(expected_turn_id) = expected_turn_id
|
|
||||||
&& expected_turn_id != active_turn_id
|
|
||||||
{
|
|
||||||
return Err(SteerInputError::ExpectedTurnMismatch {
|
|
||||||
expected: expected_turn_id.to_string(),
|
|
||||||
actual: active_turn_id.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match active_task.kind {
|
|
||||||
crate::state::TaskKind::Regular => {}
|
|
||||||
crate::state::TaskKind::Review => {
|
|
||||||
return Err(SteerInputError::ActiveTurnNotSteerable {
|
|
||||||
turn_kind: NonSteerableTurnKind::Review,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
crate::state::TaskKind::Compact => {
|
|
||||||
return Err(SteerInputError::ActiveTurnNotSteerable {
|
|
||||||
turn_kind: NonSteerableTurnKind::Compact,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.is_empty() {
|
|
||||||
return Err(SteerInputError::EmptyInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
let additional_context_input = {
|
|
||||||
let mut state = self.state.lock().await;
|
|
||||||
state.additional_context.merge(additional_context)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
|
|
||||||
active_task
|
|
||||||
.turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_responsesapi_client_metadata(responsesapi_client_metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pending_input = additional_context_input
|
|
||||||
.into_iter()
|
|
||||||
.map(ResponseItem::from)
|
|
||||||
.map(|item| self.annotate_client_response_item(item))
|
|
||||||
.map(TurnInput::ResponseItem)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
pending_input.push(TurnInput::UserInput {
|
|
||||||
content: input,
|
|
||||||
client_id: client_user_message_id.clone(),
|
|
||||||
});
|
|
||||||
if let Some(incoming_turn_metadata) = incoming_turn_metadata
|
|
||||||
&& active_task.turn_context.turn_metadata_state.root_turn_id()
|
|
||||||
!= incoming_turn_metadata.root_turn_id()
|
|
||||||
{
|
|
||||||
active_task
|
|
||||||
.turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.mark_root_turn_ambiguous();
|
|
||||||
}
|
|
||||||
self.input_queue
|
|
||||||
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
|
|
||||||
active_turn.turn_state.as_ref(),
|
|
||||||
pending_input,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(active_turn_id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn record_memory_citation_for_turn(&self, sub_id: &str) {
|
pub(crate) async fn record_memory_citation_for_turn(&self, sub_id: &str) {
|
||||||
let turn_state = self
|
let turn_state = self
|
||||||
.input_queue
|
.input_queue
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ pub(crate) struct Session {
|
|||||||
pub(crate) conversation: Arc<RealtimeConversationManager>,
|
pub(crate) conversation: Arc<RealtimeConversationManager>,
|
||||||
pub(crate) active_turn: Mutex<Option<ActiveTurn>>,
|
pub(crate) active_turn: Mutex<Option<ActiveTurn>>,
|
||||||
pub(crate) async_hook_results: async_channel::Receiver<HookCompletedEvent>,
|
pub(crate) async_hook_results: async_channel::Receiver<HookCompletedEvent>,
|
||||||
pub(crate) pending_user_message_admissions:
|
|
||||||
crate::user_message_admission::PendingUserMessageAdmissions,
|
|
||||||
pub(crate) input_queue: InputQueue,
|
pub(crate) input_queue: InputQueue,
|
||||||
pub(crate) guardian_review_session: GuardianReviewSessionManager,
|
pub(crate) guardian_review_session: GuardianReviewSessionManager,
|
||||||
pub(crate) services: SessionServices,
|
pub(crate) services: SessionServices,
|
||||||
@@ -1312,7 +1310,6 @@ impl Session {
|
|||||||
conversation: Arc::new(RealtimeConversationManager::new()),
|
conversation: Arc::new(RealtimeConversationManager::new()),
|
||||||
active_turn: Mutex::new(None),
|
active_turn: Mutex::new(None),
|
||||||
async_hook_results,
|
async_hook_results,
|
||||||
pending_user_message_admissions: Default::default(),
|
|
||||||
input_queue: InputQueue::new(),
|
input_queue: InputQueue::new(),
|
||||||
guardian_review_session: GuardianReviewSessionManager::default(),
|
guardian_review_session: GuardianReviewSessionManager::default(),
|
||||||
services,
|
services,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use super::mcp_refresh::McpRefresh;
|
|||||||
use super::turn_context::TurnEnvironment;
|
use super::turn_context::TurnEnvironment;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::agents_md_manager::AgentsMdManager;
|
use crate::agents_md_manager::AgentsMdManager;
|
||||||
use crate::codex_thread::TryStartTurnIfIdleRejectionReason;
|
|
||||||
use crate::config::ConfigBuilder;
|
use crate::config::ConfigBuilder;
|
||||||
use crate::config::ConfigOverrides;
|
use crate::config::ConfigOverrides;
|
||||||
use crate::config::test_config;
|
use crate::config::test_config;
|
||||||
@@ -30,7 +29,7 @@ use codex_config::loader::project_trust_key;
|
|||||||
use codex_config::types::McpServerConfig;
|
use codex_config::types::McpServerConfig;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_config::types::ToolSuggestDisabledTool;
|
use codex_config::types::ToolSuggestDisabledTool;
|
||||||
use core_test_support::test_codex::local_selections;
|
use core_test_support::test_codex::TurnInputRequest as ExternalTurnInputRequest;
|
||||||
|
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_http_client::ClientRouteClass;
|
use codex_http_client::ClientRouteClass;
|
||||||
@@ -67,13 +66,18 @@ use codex_protocol::permissions::FileSystemPath;
|
|||||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||||
use codex_protocol::protocol::NonSteerableTurnKind;
|
use codex_protocol::protocol::ErrorEvent;
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
|
use codex_protocol::turn_input::TurnInput as SubmittedTurnInput;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
use codex_tools::ToolSpec;
|
use codex_tools::ToolSpec;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use tracing::Span;
|
use tracing::Span;
|
||||||
|
|
||||||
use crate::connectors::AppInfo;
|
use crate::connectors::AppInfo;
|
||||||
@@ -3388,16 +3392,12 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(ExternalTurnInputRequest::user_input(vec![
|
||||||
items: vec![UserInput::Text {
|
UserInput::Text {
|
||||||
text: "fork seed".into(),
|
text: "fork seed".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
},
|
||||||
final_output_json_schema: None,
|
]))
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
// Forking reads the persisted rollout JSONL, so force the completed source turn to disk
|
// Forking reads the persisted rollout JSONL, so force the completed source turn to disk
|
||||||
@@ -3433,20 +3433,17 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
|||||||
};
|
};
|
||||||
forked
|
forked
|
||||||
.thread
|
.thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
ExternalTurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "after fork".into(),
|
text: "after fork".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -5981,7 +5978,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
conversation: Arc::new(RealtimeConversationManager::new()),
|
conversation: Arc::new(RealtimeConversationManager::new()),
|
||||||
active_turn: Mutex::new(None),
|
active_turn: Mutex::new(None),
|
||||||
async_hook_results,
|
async_hook_results,
|
||||||
pending_user_message_admissions: Default::default(),
|
|
||||||
input_queue: super::input_queue::InputQueue::new(),
|
input_queue: super::input_queue::InputQueue::new(),
|
||||||
guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(),
|
guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(),
|
||||||
services,
|
services,
|
||||||
@@ -6929,7 +6925,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn submit_with_id_captures_current_span_trace_context() {
|
async fn submit_with_trace_captures_current_span_trace_context() {
|
||||||
let (_session, _turn_context) = make_session_and_context().await;
|
let (_session, _turn_context) = make_session_and_context().await;
|
||||||
let (tx_sub, rx_sub) = async_channel::bounded(1);
|
let (tx_sub, rx_sub) = async_channel::bounded(1);
|
||||||
let (_tx_event, rx_event) = async_channel::unbounded();
|
let (_tx_event, rx_event) = async_channel::unbounded();
|
||||||
@@ -6955,14 +6951,12 @@ async fn submit_with_id_captures_current_span_trace_context() {
|
|||||||
let expected_trace = async {
|
let expected_trace = async {
|
||||||
let expected_trace =
|
let expected_trace =
|
||||||
current_span_w3c_trace_context().expect("current span should have trace context");
|
current_span_w3c_trace_context().expect("current span should have trace context");
|
||||||
io.submit_with_id(Submission {
|
io.submit_with_trace(
|
||||||
id: "sub-1".into(),
|
Op::Interrupt,
|
||||||
op: Op::Interrupt,
|
/*trace*/ None,
|
||||||
client_user_message_id: None,
|
/*parent_turn_id*/ None,
|
||||||
parent_turn_id: None,
|
/*root_turn_id*/ None,
|
||||||
root_turn_id: None,
|
)
|
||||||
trace: None,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit should succeed");
|
.expect("submit should succeed");
|
||||||
expected_trace
|
expected_trace
|
||||||
@@ -7032,7 +7026,6 @@ fn submission_dispatch_span_prefers_submission_trace_context() {
|
|||||||
submission_dispatch_span(&Submission {
|
submission_dispatch_span(&Submission {
|
||||||
id: "sub-1".into(),
|
id: "sub-1".into(),
|
||||||
op: Op::Interrupt,
|
op: Op::Interrupt,
|
||||||
client_user_message_id: None,
|
|
||||||
parent_turn_id: None,
|
parent_turn_id: None,
|
||||||
root_turn_id: None,
|
root_turn_id: None,
|
||||||
trace: Some(submission_trace),
|
trace: Some(submission_trace),
|
||||||
@@ -7061,7 +7054,6 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() {
|
|||||||
item_id: None,
|
item_id: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
client_user_message_id: None,
|
|
||||||
parent_turn_id: None,
|
parent_turn_id: None,
|
||||||
root_turn_id: None,
|
root_turn_id: None,
|
||||||
trace: None,
|
trace: None,
|
||||||
@@ -7073,78 +7065,6 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn op_kind_for_input_and_context_ops() {
|
|
||||||
assert_eq!(
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
}
|
|
||||||
.kind(),
|
|
||||||
"user_input"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Op::ThreadSettings {
|
|
||||||
thread_settings: ThreadSettingsOverrides::default(),
|
|
||||||
}
|
|
||||||
.kind(),
|
|
||||||
"thread_settings"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn user_turn_updates_approvals_reviewer() {
|
|
||||||
let (session, turn_context, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let config = session.get_config().await;
|
|
||||||
handlers::user_input_or_turn(
|
|
||||||
&session,
|
|
||||||
"sub-1".to_string(),
|
|
||||||
Op::UserInput {
|
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text: "hello".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(config.cwd.clone())),
|
|
||||||
approval_policy: Some(config.permissions.approval_policy.value()),
|
|
||||||
approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview),
|
|
||||||
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
|
||||||
summary: config.model_reasoning_summary,
|
|
||||||
personality: config.personality,
|
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
|
||||||
settings: codex_protocol::config_types::Settings {
|
|
||||||
model: turn_context.model_info.slug.clone(),
|
|
||||||
reasoning_effort: config.model_reasoning_effort.clone(),
|
|
||||||
developer_instructions: None,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*parent_turn_id*/ None,
|
|
||||||
/*root_turn_id*/ None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let state = session.state.lock().await;
|
|
||||||
assert_eq!(
|
|
||||||
state.session_configuration.approvals_reviewer,
|
|
||||||
codex_config::types::ApprovalsReviewer::AutoReview
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
session.mcp_refresh.is_pending(),
|
|
||||||
"server elicitation authority changes must refresh MCP state"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn turn_environments_set_primary_environment() {
|
async fn turn_environments_set_primary_environment() {
|
||||||
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
@@ -7426,7 +7346,6 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() {
|
|||||||
let dispatch_span = submission_dispatch_span(&Submission {
|
let dispatch_span = submission_dispatch_span(&Submission {
|
||||||
id: "sub-1".into(),
|
id: "sub-1".into(),
|
||||||
op: Op::Interrupt,
|
op: Op::Interrupt,
|
||||||
client_user_message_id: None,
|
|
||||||
parent_turn_id: None,
|
parent_turn_id: None,
|
||||||
root_turn_id: None,
|
root_turn_id: None,
|
||||||
trace: Some(submission_trace.clone()),
|
trace: Some(submission_trace.clone()),
|
||||||
@@ -7749,10 +7668,10 @@ async fn submission_loop_channel_close_aborts_active_turn_before_thread_stop_lif
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn shutdown_and_wait_allows_multiple_waiters() {
|
async fn shutdown_and_wait_allows_multiple_waiters() {
|
||||||
let (_session, _turn_context) = make_session_and_context().await;
|
let (_session, _turn_context) = make_session_and_context().await;
|
||||||
let (tx_sub, rx_sub) = async_channel::bounded(4);
|
let (tx_sub, rx_sub) = async_channel::bounded::<Submission>(4);
|
||||||
let (_tx_event, rx_event) = async_channel::unbounded();
|
let (_tx_event, rx_event) = async_channel::unbounded();
|
||||||
let session_loop_handle = tokio::spawn(async move {
|
let session_loop_handle = tokio::spawn(async move {
|
||||||
let shutdown: Submission = rx_sub.recv().await.expect("shutdown submission");
|
let shutdown = rx_sub.recv().await.expect("shutdown submission");
|
||||||
assert!(matches!(shutdown.op, Op::Shutdown));
|
assert!(matches!(shutdown.op, Op::Shutdown));
|
||||||
tokio::time::sleep(StdDuration::from_millis(50)).await;
|
tokio::time::sleep(StdDuration::from_millis(50)).await;
|
||||||
});
|
});
|
||||||
@@ -7836,11 +7755,11 @@ async fn shutdown_and_wait_shuts_down_cached_guardian_subagent() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (child_session, _child_turn_context) = make_session_and_context().await;
|
let (child_session, _child_turn_context) = make_session_and_context().await;
|
||||||
let (child_tx_sub, child_rx_sub) = async_channel::bounded(4);
|
let (child_tx_sub, child_rx_sub) = async_channel::bounded::<Submission>(4);
|
||||||
let (_child_tx_event, child_rx_event) = async_channel::unbounded();
|
let (_child_tx_event, child_rx_event) = async_channel::unbounded();
|
||||||
let (child_shutdown_tx, child_shutdown_rx) = tokio::sync::oneshot::channel();
|
let (child_shutdown_tx, child_shutdown_rx) = tokio::sync::oneshot::channel();
|
||||||
let child_session_loop_handle = tokio::spawn(async move {
|
let child_session_loop_handle = tokio::spawn(async move {
|
||||||
let shutdown: Submission = child_rx_sub
|
let shutdown = child_rx_sub
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
.expect("child shutdown submission");
|
.expect("child shutdown submission");
|
||||||
@@ -7921,11 +7840,11 @@ async fn shutdown_and_wait_shuts_down_tracked_ephemeral_guardian_review() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (child_session, _child_turn_context) = make_session_and_context().await;
|
let (child_session, _child_turn_context) = make_session_and_context().await;
|
||||||
let (child_tx_sub, child_rx_sub) = async_channel::bounded(4);
|
let (child_tx_sub, child_rx_sub) = async_channel::bounded::<Submission>(4);
|
||||||
let (_child_tx_event, child_rx_event) = async_channel::unbounded();
|
let (_child_tx_event, child_rx_event) = async_channel::unbounded();
|
||||||
let (child_shutdown_tx, child_shutdown_rx) = tokio::sync::oneshot::channel();
|
let (child_shutdown_tx, child_shutdown_rx) = tokio::sync::oneshot::channel();
|
||||||
let child_session_loop_handle = tokio::spawn(async move {
|
let child_session_loop_handle = tokio::spawn(async move {
|
||||||
let shutdown: Submission = child_rx_sub
|
let shutdown = child_rx_sub
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
.expect("child shutdown submission");
|
.expect("child shutdown submission");
|
||||||
@@ -8242,7 +8161,6 @@ where
|
|||||||
conversation: Arc::new(RealtimeConversationManager::new()),
|
conversation: Arc::new(RealtimeConversationManager::new()),
|
||||||
active_turn: Mutex::new(None),
|
active_turn: Mutex::new(None),
|
||||||
async_hook_results,
|
async_hook_results,
|
||||||
pending_user_message_admissions: Default::default(),
|
|
||||||
input_queue: super::input_queue::InputQueue::new(),
|
input_queue: super::input_queue::InputQueue::new(),
|
||||||
guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(),
|
guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(),
|
||||||
services,
|
services,
|
||||||
@@ -10425,6 +10343,26 @@ async fn abort_gracefully_emits_marker_before_turn_aborted() {
|
|||||||
assert!(rx.try_recv().is_err());
|
assert!(rx.try_recv().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn submit_steer_only(
|
||||||
|
sess: &Arc<Session>,
|
||||||
|
input: Vec<UserInput>,
|
||||||
|
expected_turn_id: &str,
|
||||||
|
) -> TurnInputSubmission {
|
||||||
|
super::turn_input::handle(
|
||||||
|
sess,
|
||||||
|
TurnInputRequest::new(SubmittedTurnInput::UserInput {
|
||||||
|
content: input,
|
||||||
|
client_id: None,
|
||||||
|
}),
|
||||||
|
TurnInputMode::Steer {
|
||||||
|
expected_turn_id: expected_turn_id.to_string(),
|
||||||
|
},
|
||||||
|
"test-submission".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("steer-only submission should be valid")
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() {
|
async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() {
|
||||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||||
@@ -10455,16 +10393,8 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
|
|||||||
text: "late pending input".to_string(),
|
text: "late pending input".to_string(),
|
||||||
text_elements: vec![text_element.clone()],
|
text_elements: vec![text_element.clone()],
|
||||||
}];
|
}];
|
||||||
sess.steer_input(
|
let submission = submit_steer_only(&sess, pending_user_input.clone(), &tc.sub_id).await;
|
||||||
pending_user_input.clone(),
|
assert!(matches!(submission, TurnInputSubmission::Steered { .. }));
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
Some(&tc.sub_id),
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("steer pending input into active turn");
|
|
||||||
|
|
||||||
sess.on_task_finished(Arc::clone(&tc), /*task_result*/ Ok(None))
|
sess.on_task_finished(Arc::clone(&tc), /*task_result*/ Ok(None))
|
||||||
.await;
|
.await;
|
||||||
@@ -10644,350 +10574,6 @@ async fn thread_idle_lifecycle_waits_for_trigger_turn_mailbox_work() {
|
|||||||
assert_eq!(0, calls.load(std::sync::atomic::Ordering::SeqCst));
|
assert_eq!(0, calls.load(std::sync::atomic::Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_rejects_active_turn_without_injecting() {
|
|
||||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(&tc),
|
|
||||||
Vec::new(),
|
|
||||||
NeverEndingTask {
|
|
||||||
kind: TaskKind::Regular,
|
|
||||||
listen_to_cancellation_token: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let item = TurnInput::ResponseItem(user_message("synthetic idle input").into());
|
|
||||||
let err = sess
|
|
||||||
.try_start_turn_if_idle(vec![item.clone()])
|
|
||||||
.await
|
|
||||||
.expect_err("active turn should reject idle-only input");
|
|
||||||
|
|
||||||
assert_eq!(TryStartTurnIfIdleRejectionReason::Busy, err.reason());
|
|
||||||
assert_eq!(vec![item], err.into_input());
|
|
||||||
assert_eq!(
|
|
||||||
(Vec::<TurnInput>::new(), None, None),
|
|
||||||
sess.input_queue.get_pending_input(&sess.active_turn).await
|
|
||||||
);
|
|
||||||
|
|
||||||
sess.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_rejects_plan_mode_without_injecting() {
|
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let mut collaboration_mode = sess.collaboration_mode().await;
|
|
||||||
collaboration_mode.mode = ModeKind::Plan;
|
|
||||||
{
|
|
||||||
let mut state = sess.state.lock().await;
|
|
||||||
state.session_configuration.collaboration_mode = collaboration_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = TurnInput::ResponseItem(user_message("synthetic idle input").into());
|
|
||||||
let err = sess
|
|
||||||
.try_start_turn_if_idle(vec![item.clone()])
|
|
||||||
.await
|
|
||||||
.expect_err("plan mode should reject automatic idle input");
|
|
||||||
|
|
||||||
assert_eq!(TryStartTurnIfIdleRejectionReason::PlanMode, err.reason());
|
|
||||||
assert_eq!(vec![item], err.into_input());
|
|
||||||
assert!(sess.active_turn.lock().await.is_none());
|
|
||||||
assert_eq!(
|
|
||||||
(Vec::<TurnInput>::new(), None, None),
|
|
||||||
sess.input_queue.get_pending_input(&sess.active_turn).await
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_accepts_user_input_in_plan_mode() {
|
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let mut collaboration_mode = sess.collaboration_mode().await;
|
|
||||||
collaboration_mode.mode = ModeKind::Plan;
|
|
||||||
{
|
|
||||||
let mut state = sess.state.lock().await;
|
|
||||||
state.session_configuration.collaboration_mode = collaboration_mode;
|
|
||||||
state.merge_connector_selection(["calendar".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
sess.try_start_turn_if_idle(vec![TurnInput::UserInput {
|
|
||||||
content: vec![UserInput::Text {
|
|
||||||
text: "queued user input".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
client_id: Some("queued-user-message".to_string()),
|
|
||||||
}])
|
|
||||||
.await
|
|
||||||
.expect("plan mode should accept user-authored idle input");
|
|
||||||
|
|
||||||
assert!(sess.state.lock().await.get_connector_selection().is_empty());
|
|
||||||
|
|
||||||
sess.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_rejects_empty_user_input_in_plan_mode() {
|
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let mut collaboration_mode = sess.collaboration_mode().await;
|
|
||||||
collaboration_mode.mode = ModeKind::Plan;
|
|
||||||
{
|
|
||||||
let mut state = sess.state.lock().await;
|
|
||||||
state.session_configuration.collaboration_mode = collaboration_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = vec![
|
|
||||||
TurnInput::UserInput {
|
|
||||||
content: Vec::new(),
|
|
||||||
client_id: Some("empty-queued-user-message".to_string()),
|
|
||||||
},
|
|
||||||
TurnInput::ResponseItem(user_message("automatic idle input").into()),
|
|
||||||
];
|
|
||||||
let error = sess
|
|
||||||
.try_start_turn_if_idle(input.clone())
|
|
||||||
.await
|
|
||||||
.expect_err("empty user input should not bypass plan mode");
|
|
||||||
|
|
||||||
assert_eq!(TryStartTurnIfIdleRejectionReason::PlanMode, error.reason());
|
|
||||||
assert_eq!(input, error.into_input());
|
|
||||||
assert!(sess.active_turn.lock().await.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_rejects_pending_trigger_turn_without_injecting() {
|
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
sess.input_queue
|
|
||||||
.enqueue_mailbox_communication(
|
|
||||||
InterAgentCommunication::new(
|
|
||||||
AgentPath::root(),
|
|
||||||
AgentPath::root(),
|
|
||||||
Vec::new(),
|
|
||||||
"pending trigger".to_string(),
|
|
||||||
/*trigger_turn*/ true,
|
|
||||||
),
|
|
||||||
/*parent_turn_id*/ None,
|
|
||||||
/*root_turn_id*/ None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let item = TurnInput::ResponseItem(user_message("synthetic idle input").into());
|
|
||||||
let err = sess
|
|
||||||
.try_start_turn_if_idle(vec![item.clone()])
|
|
||||||
.await
|
|
||||||
.expect_err("pending trigger-turn mail should reject automatic idle input");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
TryStartTurnIfIdleRejectionReason::PendingTriggerTurn,
|
|
||||||
err.reason()
|
|
||||||
);
|
|
||||||
assert_eq!(vec![item], err.into_input());
|
|
||||||
assert!(sess.active_turn.lock().await.is_none());
|
|
||||||
assert!(sess.input_queue.has_trigger_turn_mailbox_items().await);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn try_start_turn_if_idle_rejects_active_review_turn_without_injecting() {
|
|
||||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(&tc),
|
|
||||||
Vec::new(),
|
|
||||||
NeverEndingTask {
|
|
||||||
kind: TaskKind::Review,
|
|
||||||
listen_to_cancellation_token: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let item = TurnInput::ResponseItem(user_message("synthetic idle input").into());
|
|
||||||
let err = sess
|
|
||||||
.try_start_turn_if_idle(vec![item.clone()])
|
|
||||||
.await
|
|
||||||
.expect_err("active review turn should reject automatic idle input");
|
|
||||||
|
|
||||||
assert_eq!(TryStartTurnIfIdleRejectionReason::Busy, err.reason());
|
|
||||||
assert_eq!(vec![item], err.into_input());
|
|
||||||
assert_eq!(
|
|
||||||
(Vec::<TurnInput>::new(), None, None),
|
|
||||||
sess.input_queue.get_pending_input(&sess.active_turn).await
|
|
||||||
);
|
|
||||||
|
|
||||||
sess.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn steer_input_requires_active_turn() {
|
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let input = vec![UserInput::Text {
|
|
||||||
text: "steer".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
let err = sess
|
|
||||||
.steer_input(
|
|
||||||
input,
|
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
/*expected_turn_id*/ None,
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect_err("steering without active turn should fail");
|
|
||||||
|
|
||||||
assert!(matches!(err, SteerInputError::NoActiveTurn(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn steer_input_enforces_expected_turn_id() {
|
|
||||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
let input = vec![TurnInput::UserInput {
|
|
||||||
content: vec![UserInput::Text {
|
|
||||||
text: "hello".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
client_id: None,
|
|
||||||
}];
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(&tc),
|
|
||||||
input,
|
|
||||||
NeverEndingTask {
|
|
||||||
kind: TaskKind::Regular,
|
|
||||||
listen_to_cancellation_token: false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let steer_input = vec![UserInput::Text {
|
|
||||||
text: "steer".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}];
|
|
||||||
let err = sess
|
|
||||||
.steer_input(
|
|
||||||
steer_input,
|
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
Some("different-turn-id"),
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect_err("mismatched expected turn id should fail");
|
|
||||||
|
|
||||||
match err {
|
|
||||||
SteerInputError::ExpectedTurnMismatch { expected, actual } => {
|
|
||||||
assert_eq!(
|
|
||||||
(expected, actual),
|
|
||||||
("different-turn-id".to_string(), tc.sub_id.clone())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
other => panic!("unexpected error: {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn steer_input_rejects_non_regular_turns() {
|
|
||||||
for (task_kind, turn_kind) in [
|
|
||||||
(TaskKind::Review, NonSteerableTurnKind::Review),
|
|
||||||
(TaskKind::Compact, NonSteerableTurnKind::Compact),
|
|
||||||
] {
|
|
||||||
let (sess, incoming_turn_context, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
incoming_turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id("incoming-root".to_string());
|
|
||||||
let input = vec![TurnInput::UserInput {
|
|
||||||
content: vec![UserInput::Text {
|
|
||||||
text: "hello".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
client_id: None,
|
|
||||||
}];
|
|
||||||
let turn_context = sess.new_default_turn_with_sub_id("turn".to_string()).await;
|
|
||||||
turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id("active-root".to_string());
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(&turn_context),
|
|
||||||
input,
|
|
||||||
NeverEndingTask {
|
|
||||||
kind: task_kind,
|
|
||||||
listen_to_cancellation_token: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let steer_input = vec![UserInput::Text {
|
|
||||||
text: "steer".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}];
|
|
||||||
let err = sess
|
|
||||||
.steer_input(
|
|
||||||
steer_input,
|
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
/*expected_turn_id*/ None,
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
Some(incoming_turn_context.turn_metadata_state.as_ref()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect_err("steering a non-regular turn should fail");
|
|
||||||
|
|
||||||
assert_eq!(err, SteerInputError::ActiveTurnNotSteerable { turn_kind });
|
|
||||||
assert_eq!(
|
|
||||||
turn_context.turn_metadata_state.root_turn_id().as_deref(),
|
|
||||||
Some("active-root")
|
|
||||||
);
|
|
||||||
|
|
||||||
sess.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn steer_input_returns_active_turn_id() {
|
|
||||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
|
||||||
tc.turn_metadata_state
|
|
||||||
.set_root_turn_id("active-root".to_string());
|
|
||||||
let incoming_turn_context = sess
|
|
||||||
.new_default_turn_with_sub_id("incoming".to_string())
|
|
||||||
.await;
|
|
||||||
incoming_turn_context
|
|
||||||
.turn_metadata_state
|
|
||||||
.set_root_turn_id("incoming-root".to_string());
|
|
||||||
let input = vec![TurnInput::UserInput {
|
|
||||||
content: vec![UserInput::Text {
|
|
||||||
text: "hello".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}],
|
|
||||||
client_id: None,
|
|
||||||
}];
|
|
||||||
sess.spawn_task(
|
|
||||||
Arc::clone(&tc),
|
|
||||||
input,
|
|
||||||
NeverEndingTask {
|
|
||||||
kind: TaskKind::Regular,
|
|
||||||
listen_to_cancellation_token: false,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let steer_input = vec![UserInput::Text {
|
|
||||||
text: "steer".to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}];
|
|
||||||
let turn_id = sess
|
|
||||||
.steer_input(
|
|
||||||
steer_input,
|
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
Some(&tc.sub_id),
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
Some(incoming_turn_context.turn_metadata_state.as_ref()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("steering with matching expected turn id should succeed");
|
|
||||||
|
|
||||||
assert_eq!(turn_id, tc.sub_id);
|
|
||||||
assert_eq!(tc.turn_metadata_state.root_turn_id(), None);
|
|
||||||
assert!(sess.input_queue.has_pending_input(&sess.active_turn).await);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn abort_empty_active_turn_preserves_pending_input() {
|
async fn abort_empty_active_turn_preserves_pending_input() {
|
||||||
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
let (sess, _tc, _rx) = make_session_and_context_with_rx().await;
|
||||||
@@ -11150,19 +10736,16 @@ async fn steered_input_reopens_mailbox_delivery_for_current_turn() {
|
|||||||
/*root_turn_id*/ None,
|
/*root_turn_id*/ None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
sess.steer_input(
|
let submission = submit_steer_only(
|
||||||
|
&sess,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "follow up".to_string(),
|
text: "follow up".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}],
|
||||||
/*additional_context*/ Default::default(),
|
&tc.sub_id,
|
||||||
Some(&tc.sub_id),
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.expect("steered input should be accepted");
|
assert!(matches!(submission, TurnInputSubmission::Steered { .. }));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(sess.input_queue.get_pending_input(&sess.active_turn).await).0,
|
(sess.input_queue.get_pending_input(&sess.active_turn).await).0,
|
||||||
@@ -11209,19 +10792,16 @@ async fn stale_defer_mailbox_delivery_does_not_override_steered_input() {
|
|||||||
/*root_turn_id*/ None,
|
/*root_turn_id*/ None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
sess.steer_input(
|
let submission = submit_steer_only(
|
||||||
|
&sess,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "follow up".to_string(),
|
text: "follow up".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}],
|
||||||
/*additional_context*/ Default::default(),
|
&tc.sub_id,
|
||||||
Some(&tc.sub_id),
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
/*incoming_turn_metadata*/ None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.expect("steered input should be accepted");
|
assert!(matches!(submission, TurnInputSubmission::Steered { .. }));
|
||||||
|
|
||||||
sess.input_queue
|
sess.input_queue
|
||||||
.defer_mailbox_delivery_to_next_turn(&sess.active_turn, &tc.sub_id)
|
.defer_mailbox_delivery_to_next_turn(&sess.active_turn, &tc.sub_id)
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
//! Handles persistent thread-settings updates shared by standalone settings
|
||||||
|
//! submissions and turn-input submission.
|
||||||
|
|
||||||
|
use super::session::Session;
|
||||||
|
use super::session::SessionSettingsUpdate;
|
||||||
|
use crate::config::ConstraintResult;
|
||||||
|
use codex_protocol::protocol::CodexErrorInfo;
|
||||||
|
use codex_protocol::protocol::ErrorEvent;
|
||||||
|
use codex_protocol::protocol::Event;
|
||||||
|
use codex_protocol::protocol::EventMsg;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsAppliedEvent;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Applies standalone thread settings and reports invalid overrides through the
|
||||||
|
/// normal event stream.
|
||||||
|
pub(super) async fn update(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
submission_id: String,
|
||||||
|
overrides: ThreadSettingsOverrides,
|
||||||
|
) {
|
||||||
|
let updates = prepare_update(session, overrides).await;
|
||||||
|
if let Err(error) = apply_update(session, submission_id.clone(), updates).await {
|
||||||
|
session
|
||||||
|
.send_event_raw(Event {
|
||||||
|
id: submission_id,
|
||||||
|
msg: EventMsg::Error(ErrorEvent {
|
||||||
|
message: format!("invalid thread settings override: {error}"),
|
||||||
|
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts protocol overrides into the internal settings update shape.
|
||||||
|
pub(super) async fn prepare_update(
|
||||||
|
session: &Session,
|
||||||
|
overrides: ThreadSettingsOverrides,
|
||||||
|
) -> SessionSettingsUpdate {
|
||||||
|
let ThreadSettingsOverrides {
|
||||||
|
environments,
|
||||||
|
profile_workspace_roots,
|
||||||
|
approval_policy,
|
||||||
|
approvals_reviewer,
|
||||||
|
sandbox_policy,
|
||||||
|
permission_profile,
|
||||||
|
active_permission_profile,
|
||||||
|
windows_sandbox_level,
|
||||||
|
model,
|
||||||
|
effort,
|
||||||
|
summary,
|
||||||
|
service_tier,
|
||||||
|
collaboration_mode,
|
||||||
|
personality,
|
||||||
|
} = overrides;
|
||||||
|
let collaboration_mode = match collaboration_mode {
|
||||||
|
Some(collaboration_mode) => collaboration_mode,
|
||||||
|
None => {
|
||||||
|
let state = session.state.lock().await;
|
||||||
|
// Model and reasoning effort live in CollaborationMode settings today, so
|
||||||
|
// partial thread-settings updates refresh those fields on the active mode.
|
||||||
|
state
|
||||||
|
.session_configuration
|
||||||
|
.collaboration_mode
|
||||||
|
.with_updates(model, effort, /*developer_instructions*/ None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SessionSettingsUpdate {
|
||||||
|
environments,
|
||||||
|
profile_workspace_roots,
|
||||||
|
approval_policy,
|
||||||
|
approvals_reviewer,
|
||||||
|
sandbox_policy,
|
||||||
|
permission_profile,
|
||||||
|
active_permission_profile,
|
||||||
|
windows_sandbox_level,
|
||||||
|
collaboration_mode: Some(collaboration_mode),
|
||||||
|
reasoning_summary: summary,
|
||||||
|
service_tier,
|
||||||
|
personality,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies persistent settings and emits the resulting effective snapshot.
|
||||||
|
pub(super) async fn apply_update(
|
||||||
|
session: &Session,
|
||||||
|
submission_id: String,
|
||||||
|
updates: SessionSettingsUpdate,
|
||||||
|
) -> ConstraintResult<()> {
|
||||||
|
session.update_settings(updates).await?;
|
||||||
|
emit_applied(session, submission_id).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the effective thread settings after a successful update.
|
||||||
|
pub(super) async fn emit_applied(session: &Session, submission_id: String) {
|
||||||
|
let msg = applied_event(session).await;
|
||||||
|
session
|
||||||
|
.send_event_raw_without_materializing_rollout(Event {
|
||||||
|
id: submission_id,
|
||||||
|
msg,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the effective thread-settings event used by live updates and
|
||||||
|
/// synthesized fork history.
|
||||||
|
pub(super) async fn applied_event(session: &Session) -> EventMsg {
|
||||||
|
let snapshot = {
|
||||||
|
let state = session.state.lock().await;
|
||||||
|
state.session_configuration.thread_config_snapshot()
|
||||||
|
};
|
||||||
|
EventMsg::ThreadSettingsApplied(ThreadSettingsAppliedEvent {
|
||||||
|
thread_settings: snapshot.into_thread_settings_snapshot(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,581 @@
|
|||||||
|
//! Handles reply-bearing turn-input operations.
|
||||||
|
//!
|
||||||
|
//! This is the one place Core decides whether submitted input starts a turn,
|
||||||
|
//! steers an active turn, or is rejected. It replies after that decision; it
|
||||||
|
//! does not wait for user-prompt hooks, updating the in-memory model context,
|
||||||
|
//! rollout persistence, or sampling.
|
||||||
|
//!
|
||||||
|
//! Persistent thread settings apply on Started and Steered. Turn start
|
||||||
|
//! options only apply on Started.
|
||||||
|
|
||||||
|
use super::TurnInput;
|
||||||
|
use super::session::Session;
|
||||||
|
use super::session::SessionSettingsUpdate;
|
||||||
|
use super::thread_settings;
|
||||||
|
use super::turn_context::TurnContext;
|
||||||
|
use crate::state::ActiveTurn;
|
||||||
|
use crate::state::TurnState;
|
||||||
|
use crate::tasks::MailboxParentProvenance;
|
||||||
|
use crate::tasks::RegularTask;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::error::CodexErr;
|
||||||
|
use codex_protocol::error::Result as CodexResult;
|
||||||
|
use codex_protocol::models::ResponseItem;
|
||||||
|
use codex_protocol::protocol::AdditionalContextEntry;
|
||||||
|
use codex_protocol::protocol::CodexErrorInfo;
|
||||||
|
use codex_protocol::protocol::ErrorEvent;
|
||||||
|
use codex_protocol::protocol::Event;
|
||||||
|
use codex_protocol::protocol::EventMsg;
|
||||||
|
use codex_protocol::protocol::NonSteerableTurnKind;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
|
use codex_protocol::turn_input::NotSubmittedReason;
|
||||||
|
use codex_protocol::turn_input::TurnInput as SubmittedTurnInput;
|
||||||
|
use codex_protocol::turn_input::TurnInputMode;
|
||||||
|
use codex_protocol::turn_input::TurnInputRequest;
|
||||||
|
use codex_protocol::turn_input::TurnInputSubmission;
|
||||||
|
use codex_protocol::turn_input::TurnStartOptions;
|
||||||
|
use codex_protocol::user_input::UserInput;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "turn_input_tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// Thread settings and start-only options prepared before Core knows whether
|
||||||
|
/// turn input starts or steers.
|
||||||
|
///
|
||||||
|
/// Thread settings are validated up front but only applied after Core accepts
|
||||||
|
/// the input. Start-only options are only consumed by `apply_started`.
|
||||||
|
struct PreparedTurnInputSettings {
|
||||||
|
thread_settings_update: Option<SessionSettingsUpdate>,
|
||||||
|
start_options: TurnStartOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreparedTurnInputSettings {
|
||||||
|
/// Validates turn-input settings without applying them so rejected input
|
||||||
|
/// leaves the thread unchanged.
|
||||||
|
async fn prepare(
|
||||||
|
session: &Session,
|
||||||
|
thread_settings: ThreadSettingsOverrides,
|
||||||
|
start_options: TurnStartOptions,
|
||||||
|
) -> CodexResult<Self> {
|
||||||
|
let thread_settings_update = if thread_settings == ThreadSettingsOverrides::default() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let updates = thread_settings::prepare_update(session, thread_settings).await;
|
||||||
|
session
|
||||||
|
.preview_settings(&updates)
|
||||||
|
.await
|
||||||
|
.map_err(|error| CodexErr::InvalidRequest(error.to_string()))?;
|
||||||
|
Some(updates)
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
thread_settings_update,
|
||||||
|
start_options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn required_active_final_output_json_schema(&self) -> Option<&Value> {
|
||||||
|
self.start_options.final_output_json_schema.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn would_enter_plan_mode(&self) -> bool {
|
||||||
|
self.thread_settings_update
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|updates| updates.collaboration_mode.as_ref())
|
||||||
|
.is_some_and(|collaboration_mode| collaboration_mode.mode == ModeKind::Plan)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies persistent settings and start-only options before creating a
|
||||||
|
/// new turn context.
|
||||||
|
async fn apply_started(
|
||||||
|
self,
|
||||||
|
session: &Arc<Session>,
|
||||||
|
submission_id: String,
|
||||||
|
) -> CodexResult<Arc<TurnContext>> {
|
||||||
|
let TurnStartOptions {
|
||||||
|
final_output_json_schema,
|
||||||
|
parent_turn_id,
|
||||||
|
root_turn_id,
|
||||||
|
} = self.start_options;
|
||||||
|
let emit_thread_settings_applied = self.thread_settings_update.is_some();
|
||||||
|
let mut updates = self.thread_settings_update.unwrap_or_default();
|
||||||
|
updates.final_output_json_schema = Some(final_output_json_schema);
|
||||||
|
|
||||||
|
// new_turn_with_sub_id already emits an error event when settings are invalid.
|
||||||
|
let turn_context = session
|
||||||
|
.new_turn_with_sub_id(submission_id.clone(), updates)
|
||||||
|
.await?;
|
||||||
|
if emit_thread_settings_applied {
|
||||||
|
thread_settings::emit_applied(session, submission_id).await;
|
||||||
|
}
|
||||||
|
if let Some(parent_turn_id) = parent_turn_id {
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_parent_turn_id(parent_turn_id);
|
||||||
|
}
|
||||||
|
if let Some(root_turn_id) = root_turn_id {
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_root_turn_id(root_turn_id);
|
||||||
|
}
|
||||||
|
Ok(turn_context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies only persistent settings after steering succeeds. The active
|
||||||
|
/// turn keeps its existing context; subsequent turns see the update.
|
||||||
|
async fn apply_steered(self, session: &Session, submission_id: String) -> CodexResult<()> {
|
||||||
|
let Some(thread_settings_update) = self.thread_settings_update else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
thread_settings::apply_update(session, submission_id, thread_settings_update)
|
||||||
|
.await
|
||||||
|
.map_err(|error| CodexErr::InvalidRequest(error.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn handle(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
mode: TurnInputMode,
|
||||||
|
submission_id: String,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
match mode {
|
||||||
|
TurnInputMode::StartOrSteer => start_or_steer(session, request, submission_id).await,
|
||||||
|
TurnInputMode::StartIfIdle => start_if_idle(session, request, submission_id).await,
|
||||||
|
TurnInputMode::Steer { expected_turn_id } => {
|
||||||
|
steer(session, request, expected_turn_id, submission_id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_or_steer(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
submission_id: String,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
let TurnInputRequest {
|
||||||
|
input,
|
||||||
|
thread_settings,
|
||||||
|
start,
|
||||||
|
additional_context,
|
||||||
|
responsesapi_client_metadata,
|
||||||
|
..
|
||||||
|
} = request;
|
||||||
|
let SubmittedTurnInput::UserInput {
|
||||||
|
content: mut items,
|
||||||
|
client_id,
|
||||||
|
} = input
|
||||||
|
else {
|
||||||
|
return Err(CodexErr::InvalidRequest(
|
||||||
|
"only user input can steer a turn".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let can_start_root_turn = start.parent_turn_id.is_none() && start.root_turn_id.is_none();
|
||||||
|
let incoming_root_turn_id = start
|
||||||
|
.parent_turn_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|_| start.root_turn_id.clone());
|
||||||
|
let settings = PreparedTurnInputSettings::prepare(session, thread_settings, start).await?;
|
||||||
|
match session
|
||||||
|
.steer_input(
|
||||||
|
&mut items,
|
||||||
|
additional_context.clone(),
|
||||||
|
/*expected_turn_id*/ None,
|
||||||
|
settings.required_active_final_output_json_schema(),
|
||||||
|
client_id.clone(),
|
||||||
|
responsesapi_client_metadata.clone(),
|
||||||
|
incoming_root_turn_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(turn_id) => {
|
||||||
|
settings.apply_steered(session, submission_id).await?;
|
||||||
|
Ok(TurnInputSubmission::Steered { turn_id })
|
||||||
|
}
|
||||||
|
Err(NotSubmittedReason::NoActiveTurn) => {
|
||||||
|
let turn_context = settings
|
||||||
|
.apply_started(session, submission_id.clone())
|
||||||
|
.await?;
|
||||||
|
if can_start_root_turn
|
||||||
|
&& !items.is_empty()
|
||||||
|
&& turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.can_start_root_turn(&turn_context.session_source)
|
||||||
|
{
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_root_turn_id(submission_id.clone());
|
||||||
|
}
|
||||||
|
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_responsesapi_client_metadata(responsesapi_client_metadata);
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.maybe_emit_model_warnings_for_turn(turn_context.as_ref())
|
||||||
|
.await;
|
||||||
|
turn_context.session_telemetry.user_prompt(&items);
|
||||||
|
let mut task_input = merge_additional_context_input(session, additional_context).await;
|
||||||
|
if !items.is_empty() {
|
||||||
|
task_input.push(TurnInput::UserInput {
|
||||||
|
content: items,
|
||||||
|
client_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.spawn_task(turn_context, task_input, RegularTask::new())
|
||||||
|
.await;
|
||||||
|
Ok(TurnInputSubmission::Started {
|
||||||
|
turn_id: submission_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(reason) => Ok(TurnInputSubmission::NotSubmitted { reason }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_if_idle(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
submission_id: String,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
let TurnInputRequest {
|
||||||
|
input,
|
||||||
|
thread_settings,
|
||||||
|
start,
|
||||||
|
additional_context,
|
||||||
|
responsesapi_client_metadata,
|
||||||
|
..
|
||||||
|
} = request;
|
||||||
|
let has_user_input = has_nonempty_user_input(&input);
|
||||||
|
let can_start_root_turn = start.parent_turn_id.is_none() && start.root_turn_id.is_none();
|
||||||
|
if session.input_queue.has_trigger_turn_mailbox_items().await {
|
||||||
|
return Ok(TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PendingTriggerTurn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !has_user_input && session.collaboration_mode().await.mode == ModeKind::Plan {
|
||||||
|
return Ok(TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PlanMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let turn_state = {
|
||||||
|
let mut active_turn = session.active_turn.lock().await;
|
||||||
|
if active_turn.is_some() {
|
||||||
|
return Ok(TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::NotIdle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let active_turn = active_turn.get_or_insert_with(ActiveTurn::default);
|
||||||
|
Arc::clone(&active_turn.turn_state)
|
||||||
|
};
|
||||||
|
|
||||||
|
if session.input_queue.has_trigger_turn_mailbox_items().await {
|
||||||
|
session.clear_reserved_idle_turn(&turn_state).await;
|
||||||
|
session.maybe_start_turn_for_pending_work().await;
|
||||||
|
return Ok(TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PendingTriggerTurn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = match PreparedTurnInputSettings::prepare(session, thread_settings, start).await {
|
||||||
|
Ok(settings) => settings,
|
||||||
|
Err(error) => {
|
||||||
|
session.clear_reserved_idle_turn(&turn_state).await;
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Automatic work must not use persistent settings to start a turn
|
||||||
|
// whose effective collaboration mode is Plan.
|
||||||
|
if !has_user_input && settings.would_enter_plan_mode() {
|
||||||
|
session.clear_reserved_idle_turn(&turn_state).await;
|
||||||
|
return Ok(TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PlanMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let turn_context = match settings.apply_started(session, submission_id.clone()).await {
|
||||||
|
Ok(turn_context) => turn_context,
|
||||||
|
Err(error) => {
|
||||||
|
session.clear_reserved_idle_turn(&turn_state).await;
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_responsesapi_client_metadata(responsesapi_client_metadata);
|
||||||
|
}
|
||||||
|
if has_user_input
|
||||||
|
&& can_start_root_turn
|
||||||
|
&& turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.can_start_root_turn(&turn_context.session_source)
|
||||||
|
{
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_root_turn_id(submission_id.clone());
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.maybe_emit_model_warnings_for_turn(turn_context.as_ref())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut task_input = merge_additional_context_input(session, additional_context).await;
|
||||||
|
if has_user_input {
|
||||||
|
session.clear_connector_selection().await;
|
||||||
|
if let SubmittedTurnInput::UserInput { content, .. } = &input {
|
||||||
|
turn_context.session_telemetry.user_prompt(content);
|
||||||
|
}
|
||||||
|
task_input.push(pending_turn_input(input));
|
||||||
|
} else {
|
||||||
|
session
|
||||||
|
.input_queue
|
||||||
|
.extend_pending_input_for_turn_state(
|
||||||
|
turn_state.as_ref(),
|
||||||
|
vec![pending_turn_input(input)],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.start_task(
|
||||||
|
turn_context,
|
||||||
|
task_input,
|
||||||
|
RegularTask::new(),
|
||||||
|
MailboxParentProvenance::Ignore,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(TurnInputSubmission::Started {
|
||||||
|
turn_id: submission_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn steer(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
request: TurnInputRequest,
|
||||||
|
expected_turn_id: String,
|
||||||
|
submission_id: String,
|
||||||
|
) -> CodexResult<TurnInputSubmission> {
|
||||||
|
let TurnInputRequest {
|
||||||
|
input,
|
||||||
|
thread_settings,
|
||||||
|
start,
|
||||||
|
additional_context,
|
||||||
|
responsesapi_client_metadata,
|
||||||
|
..
|
||||||
|
} = request;
|
||||||
|
let SubmittedTurnInput::UserInput {
|
||||||
|
content: mut items,
|
||||||
|
client_id,
|
||||||
|
} = input
|
||||||
|
else {
|
||||||
|
return Err(CodexErr::InvalidRequest(
|
||||||
|
"only user input can steer a turn".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let incoming_root_turn_id = start
|
||||||
|
.parent_turn_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|_| start.root_turn_id.clone());
|
||||||
|
let settings = PreparedTurnInputSettings::prepare(session, thread_settings, start).await?;
|
||||||
|
match session
|
||||||
|
.steer_input(
|
||||||
|
&mut items,
|
||||||
|
additional_context,
|
||||||
|
Some(expected_turn_id.as_str()),
|
||||||
|
settings.required_active_final_output_json_schema(),
|
||||||
|
client_id,
|
||||||
|
responsesapi_client_metadata,
|
||||||
|
incoming_root_turn_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(turn_id) => {
|
||||||
|
settings.apply_steered(session, submission_id).await?;
|
||||||
|
Ok(TurnInputSubmission::Steered { turn_id })
|
||||||
|
}
|
||||||
|
Err(reason) => Ok(TurnInputSubmission::NotSubmitted { reason }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub(crate) async fn route_realtime_text_input(self: &Arc<Self>, text: String) {
|
||||||
|
let submission_id = Uuid::now_v7().to_string();
|
||||||
|
let submission = handle(
|
||||||
|
self,
|
||||||
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
|
text,
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}]),
|
||||||
|
TurnInputMode::StartOrSteer,
|
||||||
|
submission_id.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match submission {
|
||||||
|
Ok(TurnInputSubmission::Started { .. } | TurnInputSubmission::Steered { .. }) => {}
|
||||||
|
Ok(TurnInputSubmission::NotSubmitted { reason }) => {
|
||||||
|
self.send_event_raw(Event {
|
||||||
|
id: submission_id,
|
||||||
|
msg: EventMsg::Error(ErrorEvent {
|
||||||
|
message: format!("failed to submit turn input: {reason:?}"),
|
||||||
|
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.send_event_raw(Event {
|
||||||
|
id: submission_id,
|
||||||
|
msg: EventMsg::Error(error.to_error_event(/*message_prefix*/ None)),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clear_reserved_idle_turn(&self, turn_state: &Arc<tokio::sync::Mutex<TurnState>>) {
|
||||||
|
let mut active_turn_guard = self.active_turn.lock().await;
|
||||||
|
if let Some(active_turn) = active_turn_guard.as_ref()
|
||||||
|
&& active_turn.task.is_none()
|
||||||
|
&& Arc::ptr_eq(&active_turn.turn_state, turn_state)
|
||||||
|
{
|
||||||
|
*active_turn_guard = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inject additional user input into the currently active turn.
|
||||||
|
///
|
||||||
|
/// Returns the active turn id when accepted.
|
||||||
|
#[expect(
|
||||||
|
clippy::await_holding_invalid_type,
|
||||||
|
reason = "active turn checks and turn state updates must remain atomic"
|
||||||
|
)]
|
||||||
|
#[expect(
|
||||||
|
clippy::too_many_arguments,
|
||||||
|
reason = "steering carries the accepted input plus its turn-scoped metadata"
|
||||||
|
)]
|
||||||
|
async fn steer_input(
|
||||||
|
&self,
|
||||||
|
input: &mut Vec<UserInput>,
|
||||||
|
additional_context: BTreeMap<String, AdditionalContextEntry>,
|
||||||
|
expected_turn_id: Option<&str>,
|
||||||
|
required_final_output_json_schema: Option<&Value>,
|
||||||
|
client_user_message_id: Option<String>,
|
||||||
|
responsesapi_client_metadata: Option<HashMap<String, String>>,
|
||||||
|
incoming_root_turn_id: Option<Option<String>>,
|
||||||
|
) -> Result<String, NotSubmittedReason> {
|
||||||
|
let mut active = self.active_turn.lock().await;
|
||||||
|
let Some(active_turn) = active.as_mut() else {
|
||||||
|
return Err(NotSubmittedReason::NoActiveTurn);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(active_task) = active_turn.task.as_ref() else {
|
||||||
|
return Err(NotSubmittedReason::NoActiveTurn);
|
||||||
|
};
|
||||||
|
let active_turn_id = &active_task.turn_context.sub_id;
|
||||||
|
|
||||||
|
if let Some(expected_turn_id) = expected_turn_id
|
||||||
|
&& expected_turn_id != active_turn_id
|
||||||
|
{
|
||||||
|
return Err(NotSubmittedReason::ExpectedTurnMismatch {
|
||||||
|
expected: expected_turn_id.to_string(),
|
||||||
|
actual: active_turn_id.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match active_task.kind {
|
||||||
|
crate::state::TaskKind::Regular => {}
|
||||||
|
crate::state::TaskKind::Review => {
|
||||||
|
return Err(NotSubmittedReason::ActiveTurnNotSteerable {
|
||||||
|
turn_kind: NonSteerableTurnKind::Review,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
crate::state::TaskKind::Compact => {
|
||||||
|
return Err(NotSubmittedReason::ActiveTurnNotSteerable {
|
||||||
|
turn_kind: NonSteerableTurnKind::Compact,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
return Err(NotSubmittedReason::EmptyInput);
|
||||||
|
}
|
||||||
|
// Compare JSON values directly instead of serialized schema text.
|
||||||
|
// Value equality ignores object key order while preserving array and
|
||||||
|
// scalar distinctions; broader JSON Schema equivalence is out of scope.
|
||||||
|
if let Some(required_schema) = required_final_output_json_schema
|
||||||
|
&& active_task.turn_context.final_output_json_schema.as_ref() != Some(required_schema)
|
||||||
|
{
|
||||||
|
return Err(NotSubmittedReason::ActiveTurnOutputSchemaMismatch);
|
||||||
|
}
|
||||||
|
active_task
|
||||||
|
.turn_context
|
||||||
|
.session_telemetry
|
||||||
|
.user_prompt(input);
|
||||||
|
|
||||||
|
let mut pending_input = merge_additional_context_input(self, additional_context).await;
|
||||||
|
|
||||||
|
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
|
||||||
|
active_task
|
||||||
|
.turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_responsesapi_client_metadata(responsesapi_client_metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_input.push(TurnInput::UserInput {
|
||||||
|
content: std::mem::take(input),
|
||||||
|
client_id: client_user_message_id,
|
||||||
|
});
|
||||||
|
if let Some(incoming_root_turn_id) = incoming_root_turn_id
|
||||||
|
&& active_task.turn_context.turn_metadata_state.root_turn_id() != incoming_root_turn_id
|
||||||
|
{
|
||||||
|
active_task
|
||||||
|
.turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.mark_root_turn_ambiguous();
|
||||||
|
}
|
||||||
|
self.input_queue
|
||||||
|
.extend_pending_input_and_accept_mailbox_delivery_for_turn_state(
|
||||||
|
active_turn.turn_state.as_ref(),
|
||||||
|
pending_input,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(active_turn_id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_nonempty_user_input(input: &SubmittedTurnInput) -> bool {
|
||||||
|
matches!(input, SubmittedTurnInput::UserInput { content, .. } if !content.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn merge_additional_context_input(
|
||||||
|
session: &Session,
|
||||||
|
additional_context: BTreeMap<String, AdditionalContextEntry>,
|
||||||
|
) -> Vec<TurnInput> {
|
||||||
|
let additional_context_input = {
|
||||||
|
let mut state = session.state.lock().await;
|
||||||
|
state.additional_context.merge(additional_context)
|
||||||
|
};
|
||||||
|
additional_context_input
|
||||||
|
.into_iter()
|
||||||
|
.map(ResponseItem::from)
|
||||||
|
.map(|item| session.annotate_client_response_item(item))
|
||||||
|
.map(TurnInput::ResponseItem)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_turn_input(input: SubmittedTurnInput) -> TurnInput {
|
||||||
|
match input {
|
||||||
|
SubmittedTurnInput::UserInput { content, client_id } => {
|
||||||
|
TurnInput::UserInput { content, client_id }
|
||||||
|
}
|
||||||
|
SubmittedTurnInput::ResponseItem(item) => TurnInput::ResponseItem(item.into()),
|
||||||
|
SubmittedTurnInput::InterAgentCommunication(communication) => {
|
||||||
|
TurnInput::InterAgentCommunication(communication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::session::tests::make_session_and_context_with_rx;
|
||||||
|
use crate::state::TaskKind;
|
||||||
|
use crate::tasks::SessionTask;
|
||||||
|
use crate::tasks::SessionTaskResult;
|
||||||
|
use codex_protocol::AgentPath;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
|
use codex_protocol::models::ContentItem;
|
||||||
|
use codex_protocol::models::ResponseItem;
|
||||||
|
use codex_protocol::protocol::InterAgentCommunication;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
|
use codex_protocol::protocol::TurnAbortReason;
|
||||||
|
use codex_protocol::turn_input::TurnInput as SubmittedTurnInput;
|
||||||
|
use codex_protocol::user_input::UserInput;
|
||||||
|
use core_test_support::test_codex::local_selections;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct NeverEndingTask {
|
||||||
|
kind: TaskKind,
|
||||||
|
listen_to_cancellation_token: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionTask for NeverEndingTask {
|
||||||
|
fn kind(&self) -> TaskKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_name(&self) -> &'static str {
|
||||||
|
"session_task.turn_input_test"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_session: Arc<Session>,
|
||||||
|
_ctx: Arc<TurnContext>,
|
||||||
|
_input: Vec<TurnInput>,
|
||||||
|
cancellation_token: CancellationToken,
|
||||||
|
) -> SessionTaskResult {
|
||||||
|
if self.listen_to_cancellation_token {
|
||||||
|
cancellation_token.cancelled().await;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
sleep(std::time::Duration::from_secs(60)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_message(text: &str) -> ResponseItem {
|
||||||
|
ResponseItem::Message {
|
||||||
|
id: None,
|
||||||
|
role: "user".to_string(),
|
||||||
|
content: vec![ContentItem::InputText {
|
||||||
|
text: text.to_string(),
|
||||||
|
}],
|
||||||
|
phase: None,
|
||||||
|
internal_chat_message_metadata_passthrough: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn submit_start_only(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
input: SubmittedTurnInput,
|
||||||
|
) -> TurnInputSubmission {
|
||||||
|
handle(
|
||||||
|
session,
|
||||||
|
TurnInputRequest::new(input),
|
||||||
|
TurnInputMode::StartIfIdle,
|
||||||
|
"test-submission".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("start-only submission should be valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn submit_steer_only(
|
||||||
|
session: &Arc<Session>,
|
||||||
|
input: Vec<UserInput>,
|
||||||
|
expected_turn_id: &str,
|
||||||
|
) -> TurnInputSubmission {
|
||||||
|
handle(
|
||||||
|
session,
|
||||||
|
TurnInputRequest::new(SubmittedTurnInput::UserInput {
|
||||||
|
content: input,
|
||||||
|
client_id: None,
|
||||||
|
}),
|
||||||
|
TurnInputMode::Steer {
|
||||||
|
expected_turn_id: expected_turn_id.to_string(),
|
||||||
|
},
|
||||||
|
"test-submission".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("steer-only submission should be valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn accepted_input_applies_thread_settings() {
|
||||||
|
let (session, turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
let config = session.get_config().await;
|
||||||
|
handle(
|
||||||
|
&session,
|
||||||
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
|
text: "hello".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}])
|
||||||
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
|
environments: Some(local_selections(config.cwd.clone())),
|
||||||
|
approval_policy: Some(config.permissions.approval_policy.value()),
|
||||||
|
approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview),
|
||||||
|
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
||||||
|
summary: config.model_reasoning_summary,
|
||||||
|
personality: config.personality,
|
||||||
|
collaboration_mode: Some(CollaborationMode {
|
||||||
|
mode: ModeKind::Default,
|
||||||
|
settings: Settings {
|
||||||
|
model: turn_context.model_info.slug.clone(),
|
||||||
|
reasoning_effort: config.model_reasoning_effort.clone(),
|
||||||
|
developer_instructions: None,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
TurnInputMode::StartOrSteer,
|
||||||
|
"sub-1".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("submit user turn");
|
||||||
|
|
||||||
|
let state = session.state.lock().await;
|
||||||
|
assert_eq!(
|
||||||
|
state.session_configuration.approvals_reviewer,
|
||||||
|
codex_config::types::ApprovalsReviewer::AutoReview
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
session.mcp_refresh.is_pending(),
|
||||||
|
"server elicitation authority changes must refresh MCP state"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_only_rejects_active_turn_without_injecting() {
|
||||||
|
let (session, turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
session
|
||||||
|
.spawn_task(
|
||||||
|
Arc::clone(&turn_context),
|
||||||
|
Vec::new(),
|
||||||
|
NeverEndingTask {
|
||||||
|
kind: TaskKind::Regular,
|
||||||
|
listen_to_cancellation_token: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let input = SubmittedTurnInput::ResponseItem(user_message("synthetic idle input"));
|
||||||
|
let submission = submit_start_only(&session, input).await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::NotIdle,
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
(Vec::<TurnInput>::new(), None, None),
|
||||||
|
session
|
||||||
|
.input_queue
|
||||||
|
.get_pending_input(&session.active_turn)
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
|
||||||
|
session.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_only_rejects_plan_mode_without_injecting() {
|
||||||
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
let mut collaboration_mode = session.collaboration_mode().await;
|
||||||
|
collaboration_mode.mode = ModeKind::Plan;
|
||||||
|
{
|
||||||
|
let mut state = session.state.lock().await;
|
||||||
|
state.session_configuration.collaboration_mode = collaboration_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
let submission = submit_start_only(
|
||||||
|
&session,
|
||||||
|
SubmittedTurnInput::ResponseItem(user_message("synthetic idle input")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PlanMode,
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
assert!(session.active_turn.lock().await.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
(Vec::<TurnInput>::new(), None, None),
|
||||||
|
session
|
||||||
|
.input_queue
|
||||||
|
.get_pending_input(&session.active_turn)
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_only_accepts_user_input_in_plan_mode() {
|
||||||
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
let mut collaboration_mode = session.collaboration_mode().await;
|
||||||
|
collaboration_mode.mode = ModeKind::Plan;
|
||||||
|
{
|
||||||
|
let mut state = session.state.lock().await;
|
||||||
|
state.session_configuration.collaboration_mode = collaboration_mode;
|
||||||
|
state.merge_connector_selection(["calendar".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let submission = submit_start_only(
|
||||||
|
&session,
|
||||||
|
SubmittedTurnInput::UserInput {
|
||||||
|
content: vec![UserInput::Text {
|
||||||
|
text: "queued user input".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
client_id: Some("queued-user-message".to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(matches!(submission, TurnInputSubmission::Started { .. }));
|
||||||
|
assert!(
|
||||||
|
session
|
||||||
|
.state
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_connector_selection()
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
|
||||||
|
session.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_only_rejects_empty_user_input_in_plan_mode() {
|
||||||
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
let mut collaboration_mode = session.collaboration_mode().await;
|
||||||
|
collaboration_mode.mode = ModeKind::Plan;
|
||||||
|
{
|
||||||
|
let mut state = session.state.lock().await;
|
||||||
|
state.session_configuration.collaboration_mode = collaboration_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
let submission = submit_start_only(
|
||||||
|
&session,
|
||||||
|
SubmittedTurnInput::UserInput {
|
||||||
|
content: Vec::new(),
|
||||||
|
client_id: Some("empty-queued-user-message".to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PlanMode,
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
assert!(session.active_turn.lock().await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_only_rejects_pending_trigger_turn_without_injecting() {
|
||||||
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
session
|
||||||
|
.input_queue
|
||||||
|
.enqueue_mailbox_communication(
|
||||||
|
InterAgentCommunication::new(
|
||||||
|
AgentPath::root(),
|
||||||
|
AgentPath::root(),
|
||||||
|
Vec::new(),
|
||||||
|
"pending trigger".to_string(),
|
||||||
|
/*trigger_turn*/ true,
|
||||||
|
),
|
||||||
|
/*parent_turn_id*/ None,
|
||||||
|
/*root_turn_id*/ None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let submission = submit_start_only(
|
||||||
|
&session,
|
||||||
|
SubmittedTurnInput::ResponseItem(user_message("synthetic idle input")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::PendingTriggerTurn,
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
assert!(session.active_turn.lock().await.is_none());
|
||||||
|
assert!(session.input_queue.has_trigger_turn_mailbox_items().await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn steer_only_requires_active_turn() {
|
||||||
|
let (session, _turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
let submission = submit_steer_only(
|
||||||
|
&session,
|
||||||
|
vec![UserInput::Text {
|
||||||
|
text: "steer".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
"missing-turn-id",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::NoActiveTurn,
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn steer_only_enforces_expected_turn_id() {
|
||||||
|
let (session, turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
session
|
||||||
|
.spawn_task(
|
||||||
|
Arc::clone(&turn_context),
|
||||||
|
vec![TurnInput::UserInput {
|
||||||
|
content: vec![UserInput::Text {
|
||||||
|
text: "hello".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
client_id: None,
|
||||||
|
}],
|
||||||
|
NeverEndingTask {
|
||||||
|
kind: TaskKind::Regular,
|
||||||
|
listen_to_cancellation_token: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let submission = submit_steer_only(
|
||||||
|
&session,
|
||||||
|
vec![UserInput::Text {
|
||||||
|
text: "steer".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
"different-turn-id",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::ExpectedTurnMismatch {
|
||||||
|
expected: "different-turn-id".to_string(),
|
||||||
|
actual: turn_context.sub_id.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submission
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rejects_non_regular_turns() {
|
||||||
|
for (task_kind, turn_kind) in [
|
||||||
|
(TaskKind::Review, NonSteerableTurnKind::Review),
|
||||||
|
(TaskKind::Compact, NonSteerableTurnKind::Compact),
|
||||||
|
] {
|
||||||
|
let (session, incoming_turn_context, _rx) = make_session_and_context_with_rx().await;
|
||||||
|
incoming_turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_root_turn_id("incoming-root".to_string());
|
||||||
|
let turn_context = session
|
||||||
|
.new_default_turn_with_sub_id("turn".to_string())
|
||||||
|
.await;
|
||||||
|
turn_context
|
||||||
|
.turn_metadata_state
|
||||||
|
.set_root_turn_id("active-root".to_string());
|
||||||
|
session
|
||||||
|
.spawn_task(
|
||||||
|
Arc::clone(&turn_context),
|
||||||
|
vec![TurnInput::UserInput {
|
||||||
|
content: vec![UserInput::Text {
|
||||||
|
text: "hello".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
client_id: None,
|
||||||
|
}],
|
||||||
|
NeverEndingTask {
|
||||||
|
kind: task_kind,
|
||||||
|
listen_to_cancellation_token: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let steer_input = vec![UserInput::Text {
|
||||||
|
text: "steer".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}];
|
||||||
|
let steer_submission = submit_steer_only(&session, steer_input.clone(), "turn").await;
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::ActiveTurnNotSteerable { turn_kind },
|
||||||
|
},
|
||||||
|
steer_submission
|
||||||
|
);
|
||||||
|
let start_or_steer_submission = handle(
|
||||||
|
&session,
|
||||||
|
TurnInputRequest::user_input(steer_input),
|
||||||
|
TurnInputMode::StartOrSteer,
|
||||||
|
"test-submission".to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("start-or-steer submission should be valid");
|
||||||
|
assert_eq!(
|
||||||
|
TurnInputSubmission::NotSubmitted {
|
||||||
|
reason: NotSubmittedReason::ActiveTurnNotSteerable { turn_kind },
|
||||||
|
},
|
||||||
|
start_or_steer_submission
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
turn_context.turn_metadata_state.root_turn_id().as_deref(),
|
||||||
|
Some("active-root")
|
||||||
|
);
|
||||||
|
|
||||||
|
session.abort_all_tasks(TurnAbortReason::Interrupted).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,19 +115,6 @@ pub(crate) type ThreadIdGenerator = Arc<dyn Fn() -> ThreadId + Send + Sync>;
|
|||||||
fn capture_test_op(op: &Op) -> Option<Op> {
|
fn capture_test_op(op: &Op) -> Option<Op> {
|
||||||
match op {
|
match op {
|
||||||
Op::Interrupt => Some(Op::Interrupt),
|
Op::Interrupt => Some(Op::Interrupt),
|
||||||
Op::UserInput {
|
|
||||||
items,
|
|
||||||
final_output_json_schema,
|
|
||||||
responsesapi_client_metadata,
|
|
||||||
additional_context,
|
|
||||||
thread_settings,
|
|
||||||
} => Some(Op::UserInput {
|
|
||||||
items: items.clone(),
|
|
||||||
final_output_json_schema: final_output_json_schema.clone(),
|
|
||||||
responsesapi_client_metadata: responsesapi_client_metadata.clone(),
|
|
||||||
additional_context: additional_context.clone(),
|
|
||||||
thread_settings: thread_settings.clone(),
|
|
||||||
}),
|
|
||||||
Op::InterAgentCommunication { communication } => Some(Op::InterAgentCommunication {
|
Op::InterAgentCommunication { communication } => Some(Op::InterAgentCommunication {
|
||||||
communication: communication.clone(),
|
communication: communication.clone(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use codex_protocol::ThreadId;
|
|||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
|
use codex_protocol::items::TurnItem;
|
||||||
use codex_protocol::mcp::ClientMcpExtensions;
|
use codex_protocol::mcp::ClientMcpExtensions;
|
||||||
use codex_protocol::models::BaseInstructions;
|
use codex_protocol::models::BaseInstructions;
|
||||||
use codex_protocol::models::BaseInstructionsProvenance;
|
use codex_protocol::models::BaseInstructionsProvenance;
|
||||||
@@ -52,6 +53,7 @@ use codex_protocol::protocol::FileSystemPath;
|
|||||||
use codex_protocol::protocol::FileSystemSandboxEntry;
|
use codex_protocol::protocol::FileSystemSandboxEntry;
|
||||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||||
use codex_protocol::protocol::InterAgentCommunication;
|
use codex_protocol::protocol::InterAgentCommunication;
|
||||||
|
use codex_protocol::protocol::ItemCompletedEvent;
|
||||||
use codex_protocol::protocol::NetworkSandboxPolicy;
|
use codex_protocol::protocol::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
@@ -104,6 +106,27 @@ fn parse_agent_id(id: &str) -> ThreadId {
|
|||||||
ThreadId::from_string(id).expect("agent id should be valid")
|
ThreadId::from_string(id).expect("agent id should be valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_recorded_user_input(thread: &crate::CodexThread, expected: &[UserInput]) {
|
||||||
|
timeout(Duration::from_secs(5), async {
|
||||||
|
loop {
|
||||||
|
let event = thread
|
||||||
|
.next_event()
|
||||||
|
.await
|
||||||
|
.expect("event stream should stay open");
|
||||||
|
if let EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||||
|
item: TurnItem::UserMessage(item),
|
||||||
|
..
|
||||||
|
}) = event.msg
|
||||||
|
{
|
||||||
|
assert_eq!(item.content, expected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("timed out waiting for recorded user input");
|
||||||
|
}
|
||||||
|
|
||||||
fn thread_manager() -> ThreadManager {
|
fn thread_manager() -> ThreadManager {
|
||||||
ThreadManager::with_models_provider_for_tests(
|
ThreadManager::with_models_provider_for_tests(
|
||||||
CodexAuth::from_api_key("dummy"),
|
CodexAuth::from_api_key("dummy"),
|
||||||
@@ -2582,9 +2605,16 @@ async fn send_input_interrupts_before_prompt() {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(id, op)| (*id == agent_id).then_some(op))
|
.filter_map(|(id, op)| (*id == agent_id).then_some(op))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(ops_for_agent.len(), 2);
|
assert_eq!(ops_for_agent.len(), 1);
|
||||||
assert!(matches!(ops_for_agent[0], Op::Interrupt));
|
assert!(matches!(ops_for_agent[0], Op::Interrupt));
|
||||||
assert!(matches!(ops_for_agent[1], Op::UserInput { .. }));
|
wait_for_recorded_user_input(
|
||||||
|
thread.thread.as_ref(),
|
||||||
|
&[UserInput::Text {
|
||||||
|
text: "hi".to_string(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let _ = thread
|
let _ = thread
|
||||||
.thread
|
.thread
|
||||||
@@ -2621,31 +2651,20 @@ async fn send_input_accepts_structured_items() {
|
|||||||
.await
|
.await
|
||||||
.expect("send_input should succeed");
|
.expect("send_input should succeed");
|
||||||
|
|
||||||
let expected_items = vec![
|
wait_for_recorded_user_input(
|
||||||
UserInput::Mention {
|
thread.thread.as_ref(),
|
||||||
name: "drive".to_string(),
|
&[
|
||||||
path: "app://google_drive".to_string(),
|
UserInput::Mention {
|
||||||
},
|
name: "drive".to_string(),
|
||||||
UserInput::Text {
|
path: "app://google_drive".to_string(),
|
||||||
text: "read the folder".to_string(),
|
},
|
||||||
text_elements: Vec::new(),
|
UserInput::Text {
|
||||||
},
|
text: "read the folder".to_string(),
|
||||||
];
|
text_elements: Vec::new(),
|
||||||
assert!(manager.captured_ops().iter().any(|(id, op)| {
|
},
|
||||||
*id == agent_id
|
],
|
||||||
&& matches!(
|
)
|
||||||
op,
|
.await;
|
||||||
Op::UserInput {
|
|
||||||
items,
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context,
|
|
||||||
thread_settings,
|
|
||||||
} if items == &expected_items
|
|
||||||
&& additional_context.is_empty()
|
|
||||||
&& thread_settings == &Default::default()
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
|
|
||||||
let _ = thread
|
let _ = thread
|
||||||
.thread
|
.thread
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
use codex_protocol::error::Result as CodexResult;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
|
|
||||||
/// The turn that accepted a submitted user message.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum UserMessageAdmission {
|
|
||||||
/// Core installed a new turn for the submitted message.
|
|
||||||
Started { turn_id: String },
|
|
||||||
/// Core accepted the submitted message into an already-running turn.
|
|
||||||
Steered { turn_id: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct PendingUserMessageAdmissions {
|
|
||||||
pending: Mutex<HashMap<String, oneshot::Sender<CodexResult<UserMessageAdmission>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PendingUserMessageAdmissions {
|
|
||||||
pub(crate) fn register(
|
|
||||||
&self,
|
|
||||||
submission_id: String,
|
|
||||||
) -> (
|
|
||||||
PendingUserMessageAdmissionGuard<'_>,
|
|
||||||
oneshot::Receiver<CodexResult<UserMessageAdmission>>,
|
|
||||||
) {
|
|
||||||
let (sender, receiver) = oneshot::channel();
|
|
||||||
self.pending
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.insert(submission_id.clone(), sender);
|
|
||||||
(
|
|
||||||
PendingUserMessageAdmissionGuard {
|
|
||||||
admissions: self,
|
|
||||||
submission_id,
|
|
||||||
},
|
|
||||||
receiver,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn complete(
|
|
||||||
&self,
|
|
||||||
submission_id: &str,
|
|
||||||
admission: CodexResult<UserMessageAdmission>,
|
|
||||||
) {
|
|
||||||
let sender = self
|
|
||||||
.pending
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.remove(submission_id);
|
|
||||||
if let Some(sender) = sender {
|
|
||||||
let _ = sender.send(admission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&self, submission_id: &str) {
|
|
||||||
self.pending
|
|
||||||
.lock()
|
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
||||||
.remove(submission_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct PendingUserMessageAdmissionGuard<'a> {
|
|
||||||
admissions: &'a PendingUserMessageAdmissions,
|
|
||||||
submission_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for PendingUserMessageAdmissionGuard<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.admissions.remove(&self.submission_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@ use codex_core::CodexThread;
|
|||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
use codex_core::ThreadManager;
|
use codex_core::ThreadManager;
|
||||||
use codex_core::TimeProvider;
|
use codex_core::TimeProvider;
|
||||||
|
pub use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::resolve_installation_id;
|
use codex_core::resolve_installation_id;
|
||||||
use codex_core::shell::Shell;
|
use codex_core::shell::Shell;
|
||||||
@@ -37,6 +38,9 @@ use codex_model_provider_info::ModelProviderInfo;
|
|||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_models_manager::manager::SharedModelsManager;
|
use codex_models_manager::manager::SharedModelsManager;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::mcp::ClientMcpExtensions;
|
use codex_protocol::mcp::ClientMcpExtensions;
|
||||||
use codex_protocol::mcp::OPENAI_FORM_EXTENSION_ID;
|
use codex_protocol::mcp::OPENAI_FORM_EXTENSION_ID;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
@@ -44,7 +48,6 @@ use codex_protocol::openai_models::ModelInfo;
|
|||||||
use codex_protocol::openai_models::ModelsResponse;
|
use codex_protocol::openai_models::ModelsResponse;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::RealtimeConversationVersion as RealtimeWsVersion;
|
use codex_protocol::protocol::RealtimeConversationVersion as RealtimeWsVersion;
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||||
@@ -870,16 +873,10 @@ impl TestCodex {
|
|||||||
/// Submits a text turn without changing the current thread settings.
|
/// Submits a text turn without changing the current thread settings.
|
||||||
pub async fn submit_text_turn(&self, prompt: &str) -> Result<()> {
|
pub async fn submit_text_turn(&self, prompt: &str) -> Result<()> {
|
||||||
self.codex
|
self.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: prompt.into(),
|
||||||
text: prompt.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&self.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&self.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1007,31 +1004,28 @@ impl TestCodex {
|
|||||||
TurnEnvironmentSelections::new(self.config.cwd.clone(), environments)
|
TurnEnvironmentSelections::new(self.config.cwd.clone(), environments)
|
||||||
});
|
});
|
||||||
self.codex
|
self.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: turn_environment_selections,
|
environments: turn_environment_selections,
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
service_tier,
|
service_tier,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_id = wait_for_event_match(&self.codex, |event| match event {
|
let turn_id = wait_for_event_match(&self.codex, |event| match event {
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ExecCommandStatus;
|
use codex_protocol::protocol::ExecCommandStatus;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_assistant_message;
|
use core_test_support::responses::ev_assistant_message;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -21,6 +24,7 @@ use core_test_support::responses::sse;
|
|||||||
use core_test_support::responses::start_mock_server;
|
use core_test_support::responses::start_mock_server;
|
||||||
use core_test_support::test_codex::test_codex;
|
use core_test_support::test_codex::test_codex;
|
||||||
use core_test_support::test_codex::turn_permission_fields;
|
use core_test_support::test_codex::turn_permission_fields;
|
||||||
|
use core_test_support::test_codex::TurnInputRequest;
|
||||||
use core_test_support::wait_for_event;
|
use core_test_support::wait_for_event;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -116,30 +120,24 @@ async fn windows_exec_server_runs_with_native_shell_and_cwd() -> Result<()> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
|
||||||
text: "run the Windows smoke command".to_string(),
|
text: "run the Windows smoke command".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}]).with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(environments),
|
environments: Some(environments),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}))
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut begin = None;
|
let mut begin = None;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -45,16 +46,10 @@ async fn interrupt_long_running_tool_emits_turn_aborted() {
|
|||||||
|
|
||||||
// Kick off a turn that triggers the function call.
|
// Kick off a turn that triggers the function call.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "start sleep".into(),
|
||||||
text: "start sleep".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -102,16 +97,10 @@ async fn interrupt_tool_records_history_entries() {
|
|||||||
let codex = Arc::clone(&fixture.codex);
|
let codex = Arc::clone(&fixture.codex);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "start history recording".into(),
|
||||||
text: "start history recording".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -123,16 +112,10 @@ async fn interrupt_tool_records_history_entries() {
|
|||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "follow up".into(),
|
||||||
text: "follow up".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -206,16 +189,10 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
|
|||||||
let codex = Arc::clone(&fixture.codex);
|
let codex = Arc::clone(&fixture.codex);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "start interrupt marker".into(),
|
||||||
text: "start interrupt marker".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -227,16 +204,10 @@ async fn interrupt_persists_turn_aborted_marker_in_next_request() {
|
|||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnAborted(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "follow up".into(),
|
||||||
text: "follow up".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_protocol::items::TurnItem;
|
use codex_protocol::items::TurnItem;
|
||||||
use codex_protocol::protocol::AdditionalContextEntry;
|
use codex_protocol::protocol::AdditionalContextEntry;
|
||||||
use codex_protocol::protocol::AdditionalContextKind;
|
use codex_protocol::protocol::AdditionalContextKind;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ItemCompletedEvent;
|
use codex_protocol::protocol::ItemCompletedEvent;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::context_snapshot;
|
use core_test_support::context_snapshot;
|
||||||
use core_test_support::context_snapshot::ContextSnapshotOptions;
|
use core_test_support::context_snapshot::ContextSnapshotOptions;
|
||||||
@@ -36,14 +36,12 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "inspect the active tab".to_string(),
|
text: "inspect the active tab".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"browser_info".to_string(),
|
"browser_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -58,9 +56,8 @@ async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Re
|
|||||||
kind: AdditionalContextKind::Application,
|
kind: AdditionalContextKind::Application,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user_item = wait_for_event_match(&test.codex, |event| match event {
|
let user_item = wait_for_event_match(&test.codex, |event| match event {
|
||||||
@@ -134,13 +131,7 @@ async fn external_context_like_user_text_remains_a_user_message_item() -> Result
|
|||||||
};
|
};
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![user_input.clone()]))
|
||||||
items: vec![user_input.clone()],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::new(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user_item = wait_for_event_match(&test.codex, |event| match event {
|
let user_item = wait_for_event_match(&test.codex, |event| match event {
|
||||||
@@ -179,14 +170,12 @@ async fn additional_context_trust_controls_message_role() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "inspect context".to_string(),
|
text: "inspect context".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"browser_info".to_string(),
|
"browser_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -201,9 +190,8 @@ async fn additional_context_trust_controls_message_role() -> Result<()> {
|
|||||||
kind: AdditionalContextKind::Application,
|
kind: AdditionalContextKind::Application,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -259,16 +247,13 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re
|
|||||||
)]);
|
)]);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "first turn".to_string(),
|
text: "first turn".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(additional_context.clone()),
|
||||||
responsesapi_client_metadata: None,
|
)
|
||||||
additional_context: additional_context.clone(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -276,16 +261,13 @@ async fn additional_context_is_deduplicated_between_turns_while_retained() -> Re
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "second turn".to_string(),
|
text: "second turn".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(additional_context),
|
||||||
responsesapi_client_metadata: None,
|
)
|
||||||
additional_context,
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -337,14 +319,12 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "first turn".to_string(),
|
text: "first turn".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"automation_info".to_string(),
|
"automation_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -359,9 +339,8 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
kind: AdditionalContextKind::Untrusted,
|
kind: AdditionalContextKind::Untrusted,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -369,14 +348,12 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "second turn".to_string(),
|
text: "second turn".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"automation_info".to_string(),
|
"automation_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -391,9 +368,8 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
kind: AdditionalContextKind::Untrusted,
|
kind: AdditionalContextKind::Untrusted,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -401,14 +377,12 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "third turn".to_string(),
|
text: "third turn".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"automation_info".to_string(),
|
"automation_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -430,9 +404,8 @@ async fn additional_context_removes_one_value_while_adding_another() -> Result<(
|
|||||||
kind: AdditionalContextKind::Untrusted,
|
kind: AdditionalContextKind::Untrusted,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
@@ -497,14 +470,12 @@ async fn additional_context_values_are_truncated_before_model_input() -> Result<
|
|||||||
format!("<automation_info>{long_automation_value}</automation_info>");
|
format!("<automation_info>{long_automation_value}</automation_info>");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "summarize context".to_string(),
|
text: "summarize context".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_additional_context(BTreeMap::from([
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: BTreeMap::from([
|
|
||||||
(
|
(
|
||||||
"automation_info".to_string(),
|
"automation_info".to_string(),
|
||||||
AdditionalContextEntry {
|
AdditionalContextEntry {
|
||||||
@@ -519,9 +490,8 @@ async fn additional_context_values_are_truncated_before_model_input() -> Result<
|
|||||||
kind: AdditionalContextKind::Untrusted,
|
kind: AdditionalContextKind::Untrusted,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]),
|
])),
|
||||||
thread_settings: Default::default(),
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| {
|
wait_for_event_match(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
matches!(event, EventMsg::TurnComplete(_)).then_some(())
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::openai_models::ToolMode;
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::WebSocketConnectionConfig;
|
use core_test_support::responses::WebSocketConnectionConfig;
|
||||||
@@ -48,19 +48,16 @@ async fn websocket_model_switch_to_responses_lite_omits_top_level_tools() -> Res
|
|||||||
|
|
||||||
test.submit_turn("non-lite turn").await?;
|
test.submit_turn("non-lite turn").await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "lite turn".into(),
|
text: "lite turn".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
model: Some("gpt-5.4".to_string()),
|
model: Some("gpt-5.4".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Result;
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use codex_core::ForkSnapshot;
|
use codex_core::ForkSnapshot;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||||
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
||||||
@@ -149,16 +150,10 @@ fn assert_single_instruction_fragment(request: &responses::ResponsesRequest, exp
|
|||||||
|
|
||||||
async fn submit_thread_turn(thread: &Arc<codex_core::CodexThread>, prompt: &str) -> Result<()> {
|
async fn submit_thread_turn(thread: &Arc<codex_core::CodexThread>, prompt: &str) -> Result<()> {
|
||||||
thread
|
thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: prompt.to_string(),
|
||||||
text: prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -496,16 +491,10 @@ async fn loads_user_instructions_without_a_primary_environment() -> Result<()> {
|
|||||||
|
|
||||||
no_environment_thread
|
no_environment_thread
|
||||||
.thread
|
.thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "inspect global instructions without an environment".to_string(),
|
||||||
text: "inspect global instructions without an environment".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&no_environment_thread.thread, |event| {
|
wait_for_event(&no_environment_thread.thread, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use core_test_support::responses::ev_apply_patch_custom_tool_call;
|
use core_test_support::responses::ev_apply_patch_custom_tool_call;
|
||||||
use core_test_support::responses::ev_apply_patch_shell_command_call_via_heredoc;
|
use core_test_support::responses::ev_apply_patch_shell_command_call_via_heredoc;
|
||||||
use core_test_support::responses::ev_shell_command_call;
|
use core_test_support::responses::ev_shell_command_call;
|
||||||
@@ -19,6 +20,9 @@ use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
|||||||
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
|
||||||
use codex_exec_server::RemoveOptions;
|
use codex_exec_server::RemoveOptions;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::permissions::FileSystemAccessMode;
|
use codex_protocol::permissions::FileSystemAccessMode;
|
||||||
use codex_protocol::permissions::FileSystemPath;
|
use codex_protocol::permissions::FileSystemPath;
|
||||||
@@ -28,8 +32,8 @@ use codex_protocol::permissions::FileSystemSpecialPath;
|
|||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -99,29 +103,26 @@ async fn submit_without_wait_with_turn_permissions(
|
|||||||
let test = harness.test();
|
let test = harness.test();
|
||||||
let session_model = test.session_configured.model.clone();
|
let session_model = test.session_configured.model.clone();
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1888,15 +1889,12 @@ async fn apply_patch_turn_diff_tracks_local_and_remote_environment_paths() -> Re
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "apply matching patches to local and remote environments".into(),
|
text: "apply matching patches to local and remote environments".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
environments,
|
environments,
|
||||||
@@ -1904,17 +1902,17 @@ async fn apply_patch_turn_diff_tracks_local_and_remote_environment_paths() -> Re
|
|||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut last_diff = None;
|
let mut last_diff = None;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::types::ApprovalsReviewer;
|
use codex_config::types::ApprovalsReviewer;
|
||||||
use codex_core::CodexThread;
|
use codex_core::CodexThread;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::config::ThreadStoreConfig;
|
use codex_core::config::ThreadStoreConfig;
|
||||||
use codex_core::sandboxing::SandboxPermissions;
|
use codex_core::sandboxing::SandboxPermissions;
|
||||||
@@ -12,6 +13,9 @@ use codex_models_manager::bundled_models_response;
|
|||||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||||
use codex_protocol::approvals::NetworkPolicyAmendment;
|
use codex_protocol::approvals::NetworkPolicyAmendment;
|
||||||
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::permissions::FileSystemAccessMode;
|
use codex_protocol::permissions::FileSystemAccessMode;
|
||||||
@@ -28,6 +32,7 @@ use codex_protocol::protocol::GranularApprovalConfig;
|
|||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::managed_network_requirements_loader;
|
use core_test_support::managed_network_requirements_loader;
|
||||||
use core_test_support::responses::ev_apply_patch_custom_tool_call;
|
use core_test_support::responses::ev_apply_patch_custom_tool_call;
|
||||||
@@ -658,30 +663,27 @@ async fn submit_turn(
|
|||||||
let session_model = test.session_configured.model.clone();
|
let session_model = test.session_configured.model.clone();
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -695,29 +697,26 @@ async fn submit_turn_preserving_active_permission_profile(
|
|||||||
let session_model = test.session_configured.model.clone();
|
let session_model = test.session_configured.model.clone();
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -2699,31 +2698,28 @@ async fn env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.cwd.path());
|
turn_permission_fields(permission_profile, test.cwd.path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run nested env zsh script through python".into(),
|
text: "run nested env zsh script through python".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let approval_event = wait_for_event_with_timeout(
|
let approval_event = wait_for_event_with_timeout(
|
||||||
@@ -2843,31 +2839,28 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.cwd.path());
|
turn_permission_fields(permission_profile, test.cwd.path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run allowed touch under zsh fork".into(),
|
text: "run allowed touch under zsh fork".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_completion_without_approval(&test).await;
|
wait_for_completion_without_approval(&test).await;
|
||||||
@@ -3760,31 +3753,28 @@ allow_local_binding = true
|
|||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
let session_model = test.session_configured.model.clone();
|
let session_model = test.session_configured.model.clone();
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "deny-read network retry".into(),
|
text: "deny-read network retry".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(turn_sandbox_policy),
|
sandbox_policy: Some(turn_sandbox_policy),
|
||||||
permission_profile: turn_permission_profile,
|
permission_profile: turn_permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Result;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolNamespaceSpec;
|
use codex_protocol::dynamic_tools::DynamicToolNamespaceSpec;
|
||||||
@@ -105,16 +106,10 @@ async fn dynamic_tool_audio_exceeding_the_output_budget_is_omitted() -> Result<(
|
|||||||
test.session_configured = new_thread.session_configured;
|
test.session_configured = new_thread.session_configured;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Return a recording".to_string(),
|
||||||
text: "Return a recording".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let EventMsg::DynamicToolCallRequest(request) = wait_for_event(&test.codex, |event| {
|
let EventMsg::DynamicToolCallRequest(request) = wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::DynamicToolCallRequest(_))
|
matches!(event, EventMsg::DynamicToolCallRequest(_))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -19,6 +20,7 @@ use codex_protocol::openai_models::default_input_modalities;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -165,7 +167,7 @@ async fn remote_model_override_uses_catalog_model_for_strict_auto_review() -> Re
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(model.to_string()),
|
model: Some(model.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -176,22 +178,19 @@ async fn remote_model_override_uses_catalog_model_for_strict_auto_review() -> Re
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::read_only(), cwd_path.as_path());
|
turn_permission_fields(PermissionProfile::read_only(), cwd_path.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run the Guardian model override check".into(),
|
text: "run the Guardian model override check".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_path)),
|
environments: Some(local_selections(cwd_path)),
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let permissions_request = wait_for_event(&codex, |event| {
|
let permissions_request = wait_for_event(&codex, |event| {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_models_manager::manager::RefreshStrategy;
|
use codex_models_manager::manager::RefreshStrategy;
|
||||||
@@ -9,7 +10,6 @@ use codex_protocol::openai_models::ModelsResponse;
|
|||||||
use codex_protocol::openai_models::PermissionMessages;
|
use codex_protocol::openai_models::PermissionMessages;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -82,16 +82,10 @@ async fn catalog_permission_message_loaded_from_remote_models_is_sent() -> Resul
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".to_string(),
|
||||||
text: "hello".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
+165
-391
@@ -5,6 +5,7 @@ use codex_core::Prompt;
|
|||||||
use codex_core::ResponseEvent;
|
use codex_core::ResponseEvent;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
use codex_core::ThreadManager;
|
use codex_core::ThreadManager;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::X_CODEX_ROUTING_HINT_HEADER;
|
use codex_core::X_CODEX_ROUTING_HINT_HEADER;
|
||||||
use codex_core::resolve_installation_id;
|
use codex_core::resolve_installation_id;
|
||||||
use codex_core::thread_store_from_config;
|
use codex_core::thread_store_from_config;
|
||||||
@@ -53,6 +54,7 @@ use codex_protocol::protocol::Op;
|
|||||||
use codex_protocol::protocol::SessionMeta;
|
use codex_protocol::protocol::SessionMeta;
|
||||||
use codex_protocol::protocol::SessionMetaLine;
|
use codex_protocol::protocol::SessionMetaLine;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::TestCodexResponsesRequestKind;
|
use core_test_support::TestCodexResponsesRequestKind;
|
||||||
use core_test_support::apps_test_server::AppsTestServer;
|
use core_test_support::apps_test_server::AppsTestServer;
|
||||||
@@ -318,16 +320,10 @@ async fn non_openai_responses_requests_include_item_ids_without_passthrough_meta
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -379,15 +375,9 @@ async fn sends_audio_urls_to_responses() {
|
|||||||
let audio_url = "data:audio/wav;base64,AAAA";
|
let audio_url = "data:audio/wav;base64,AAAA";
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Audio {
|
||||||
items: vec![UserInput::Audio {
|
audio_url: audio_url.to_string(),
|
||||||
audio_url: audio_url.to_string(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -430,15 +420,9 @@ async fn sends_local_audio_to_responses() -> anyhow::Result<()> {
|
|||||||
std::fs::write(&audio_path, b"audio")?;
|
std::fs::write(&audio_path, b"audio")?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::LocalAudio {
|
||||||
items: vec![UserInput::LocalAudio {
|
path: audio_path.clone(),
|
||||||
path: audio_path.clone(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -953,16 +937,10 @@ 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.
|
// 2) Submit new input; the request body must include the prior items, then initial context, then new user input.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1347,16 +1325,10 @@ async fn includes_session_id_thread_id_and_model_headers_in_request() {
|
|||||||
let expected_thread_id = test.session_configured.thread_id;
|
let expected_thread_id = test.session_configured.thread_id;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1613,16 +1585,10 @@ async fn includes_base_instructions_override_in_request() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1670,16 +1636,10 @@ async fn chatgpt_auth_sends_correct_request() {
|
|||||||
let expected_thread_id = test.session_configured.thread_id;
|
let expected_thread_id = test.session_configured.thread_id;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1809,16 +1769,10 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
|
|||||||
.expect("create new conversation");
|
.expect("create new conversation");
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1848,16 +1802,10 @@ async fn includes_user_instructions_message_in_request() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1942,16 +1890,10 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2005,16 +1947,10 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2064,16 +2000,10 @@ async fn omits_apps_guidance_when_configured_off() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2141,16 +2071,10 @@ async fn omits_apps_guidance_when_orchestrator_mcp_is_disabled() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2223,16 +2147,10 @@ async fn omits_environment_context_when_configured_off() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2265,16 +2183,10 @@ async fn includes_configured_max_effort_in_request() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2307,16 +2219,10 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> {
|
|||||||
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2350,16 +2256,10 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info
|
|||||||
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2401,15 +2301,12 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(config.cwd.clone())),
|
environments: Some(local_selections(config.cwd.clone())),
|
||||||
approval_policy: Some(config.permissions.approval_policy.value()),
|
approval_policy: Some(config.permissions.approval_policy.value()),
|
||||||
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
||||||
@@ -2420,8 +2317,8 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
|
|||||||
),
|
),
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -2460,16 +2357,10 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2535,16 +2426,10 @@ async fn model_without_summary_parameter_support_omits_configured_summary() -> a
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -2582,16 +2467,10 @@ async fn sequential_cutoff_is_omitted_for_non_openai_provider() -> anyhow::Resul
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2626,16 +2505,10 @@ async fn responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -2687,30 +2560,27 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(config.cwd.clone())),
|
environments: Some(local_selections(config.cwd.clone())),
|
||||||
approval_policy: Some(config.permissions.approval_policy.value()),
|
approval_policy: Some(config.permissions.approval_policy.value()),
|
||||||
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
sandbox_policy: Some(config.legacy_sandbox_policy()),
|
||||||
summary: Some(ReasoningSummary::Concise),
|
summary: Some(ReasoningSummary::Concise),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_configured.model,
|
model: session_configured.model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2747,16 +2617,10 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2805,16 +2669,10 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2844,16 +2702,10 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> {
|
|||||||
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
let TestCodex { codex, .. } = test_codex().with_model("gpt-5.4").build(&server).await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2892,16 +2744,10 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2939,16 +2785,10 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2993,16 +2833,10 @@ async fn includes_developer_instructions_message_in_request() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -3320,16 +3154,10 @@ async fn token_count_includes_rate_limits_snapshot() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -3475,17 +3303,11 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
|
|||||||
"rate_limit_reached_type": "workspace_member_usage_limit_reached"
|
"rate_limit_reached_type": "workspace_member_usage_limit_reached"
|
||||||
});
|
});
|
||||||
|
|
||||||
let submission_id = codex
|
let submission = codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submission should succeed while emitting usage limit error events");
|
.expect("submission should succeed while emitting usage limit error events");
|
||||||
|
|
||||||
@@ -3509,7 +3331,7 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
error_event.message.contains("spend cap set by the owner"),
|
error_event.message.contains("spend cap set by the owner"),
|
||||||
"unexpected error message for submission {submission_id}: {}",
|
"unexpected error message for submission {submission:?}: {}",
|
||||||
error_event.message
|
error_event.message
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3553,31 +3375,19 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "seed turn".into(),
|
||||||
text: "seed turn".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "trigger context window".into(),
|
||||||
text: "trigger context window".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let token_event = wait_for_event(&codex, |event| {
|
let token_event = wait_for_event(&codex, |event| {
|
||||||
@@ -3653,16 +3463,10 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res
|
|||||||
.build(&server)
|
.build(&server)
|
||||||
.await?;
|
.await?;
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "trigger incomplete".into(),
|
||||||
text: "trigger incomplete".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
|
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
|
||||||
@@ -3765,16 +3569,10 @@ async fn azure_overrides_assign_properties_used_for_responses_url() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -3855,16 +3653,10 @@ async fn env_var_overrides_loaded_auth() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -3921,48 +3713,30 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
|
|||||||
|
|
||||||
// Turn 1: user sends U1; wait for completion.
|
// Turn 1: user sends U1; wait for completion.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "U1".into(),
|
||||||
text: "U1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
// Turn 2: user sends U2; wait for completion.
|
// Turn 2: user sends U2; wait for completion.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "U2".into(),
|
||||||
text: "U2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
// Turn 3: user sends U3; wait for completion.
|
// Turn 3: user sends U3; wait for completion.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "U3".into(),
|
||||||
text: "U3".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use codex_core::ModelClient;
|
|||||||
use codex_core::ModelClientSession;
|
use codex_core::ModelClientSession;
|
||||||
use codex_core::Prompt;
|
use codex_core::Prompt;
|
||||||
use codex_core::ResponseEvent;
|
use codex_core::ResponseEvent;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::X_CODEX_ROUTING_HINT_HEADER;
|
use codex_core::X_CODEX_ROUTING_HINT_HEADER;
|
||||||
use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
|
use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
|
||||||
use codex_core::test_support::with_parent_turn;
|
use codex_core::test_support::with_parent_turn;
|
||||||
@@ -33,7 +34,6 @@ use codex_protocol::openai_models::ModelInfo;
|
|||||||
use codex_protocol::openai_models::ModelServiceTier;
|
use codex_protocol::openai_models::ModelServiceTier;
|
||||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::W3cTraceContext;
|
use codex_protocol::protocol::W3cTraceContext;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -1609,18 +1609,12 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() {
|
|||||||
.await
|
.await
|
||||||
.expect("build websocket codex");
|
.expect("build websocket codex");
|
||||||
|
|
||||||
let submission_id = test
|
let submission = test
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submission should succeed while emitting usage limit error events");
|
.expect("submission should succeed while emitting usage limit error events");
|
||||||
|
|
||||||
@@ -1667,7 +1661,7 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() {
|
|||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
error_event.message.contains("spend cap set by the owner"),
|
error_event.message.contains("spend cap set by the owner"),
|
||||||
"unexpected error message for submission {submission_id}: {}",
|
"unexpected error message for submission {submission:?}: {}",
|
||||||
error_event.message
|
error_event.message
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1704,18 +1698,12 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() {
|
|||||||
.await
|
.await
|
||||||
.expect("build websocket codex");
|
.expect("build websocket codex");
|
||||||
|
|
||||||
let submission_id = test
|
let submission = test
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submission should succeed while emitting invalid request events");
|
.expect("submission should succeed while emitting invalid request events");
|
||||||
|
|
||||||
@@ -1728,7 +1716,7 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() {
|
|||||||
.message
|
.message
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
.contains("does not support image inputs"),
|
.contains("does not support image inputs"),
|
||||||
"unexpected error message for submission {submission_id}: {}",
|
"unexpected error message for submission {submission:?}: {}",
|
||||||
error_event.message
|
error_event.message
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
|||||||
use codex_config::types::McpServerConfig;
|
use codex_config::types::McpServerConfig;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::CurrentTimeReminderConfig;
|
use codex_core::config::CurrentTimeReminderConfig;
|
||||||
use codex_extension_api::ExtensionData;
|
use codex_extension_api::ExtensionData;
|
||||||
@@ -20,6 +21,9 @@ use codex_features::CurrentTimeSource;
|
|||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::config_types::WebSearchMode;
|
use codex_protocol::config_types::WebSearchMode;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
||||||
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;
|
||||||
@@ -33,6 +37,7 @@ use codex_protocol::openai_models::ToolMode;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_tools::FreeformTool;
|
use codex_tools::FreeformTool;
|
||||||
use codex_tools::FreeformToolFormat;
|
use codex_tools::FreeformToolFormat;
|
||||||
@@ -283,16 +288,10 @@ async fn run_unavailable_code_mode_turn(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "list available tools".to_string(),
|
||||||
text: "list available tools".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
@@ -2141,16 +2140,10 @@ async fn code_mode_wait_timeout_reconnects_on_next_exec() -> Result<()> {
|
|||||||
.completion;
|
.completion;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "wait for the stalled cell".to_string(),
|
||||||
text: "wait for the stalled cell".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_match(&test.codex, |event| match event {
|
wait_for_event_match(&test.codex, |event| match event {
|
||||||
EventMsg::RawResponseItem(raw) => match &raw.item {
|
EventMsg::RawResponseItem(raw) => match &raw.item {
|
||||||
@@ -3387,16 +3380,10 @@ async fn code_mode_interrupt_terminates_active_cells_and_nested_tools() -> Resul
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "start a long-running nested tool".to_string(),
|
||||||
text: "start a long-running nested tool".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let active_cell_id = tokio::time::timeout(Duration::from_secs(10), started_rx).await??;
|
let active_cell_id = tokio::time::timeout(Duration::from_secs(10), started_rx).await??;
|
||||||
|
|
||||||
@@ -5006,15 +4993,12 @@ text(JSON.stringify({
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "inspect and call normalized dynamic tools".to_string(),
|
text: "inspect and call normalized dynamic tools".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
||||||
cwd,
|
cwd,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -5022,17 +5006,17 @@ text(JSON.stringify({
|
|||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_id = wait_for_event_match(&test.codex, |event| match event {
|
let turn_id = wait_for_event_match(&test.codex, |event| match event {
|
||||||
@@ -5229,15 +5213,12 @@ text(
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "use exec to inspect and call hidden tools".into(),
|
text: "use exec to inspect and call hidden tools".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
environments: Some(codex_protocol::protocol::TurnEnvironmentSelections::new(
|
||||||
cwd,
|
cwd,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -5245,17 +5226,17 @@ text(
|
|||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_id = wait_for_event_match(&test.codex, |event| match event {
|
let turn_id = wait_for_event_match(&test.codex, |event| match event {
|
||||||
|
|||||||
@@ -3,13 +3,18 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -111,29 +116,26 @@ async fn submit_turn(test: &TestCodex, permission_profile: PermissionProfile) ->
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run a code-mode tool that needs user input".into(),
|
text: "run a code-mode tool that needs user input".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(wait_for_event_match(&test.codex, |event| match event {
|
Ok(wait_for_event_match(&test.codex, |event| match event {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
@@ -8,6 +9,7 @@ use codex_protocol::protocol::EventMsg;
|
|||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewRequest;
|
use codex_protocol::protocol::ReviewRequest;
|
||||||
use codex_protocol::protocol::ReviewTarget;
|
use codex_protocol::protocol::ReviewTarget;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
use core_test_support::PathExt;
|
use core_test_support::PathExt;
|
||||||
@@ -410,20 +412,17 @@ async fn guardian_delegate_rejects_escalation_requests_without_prompting() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "Trigger Guardian review of an escalated command".to_string(),
|
text: "Trigger Guardian review of an escalated command".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await
|
.await
|
||||||
.expect("submit guardian-reviewed command");
|
.expect("submit guardian-reviewed command");
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_models_manager::model_info::model_info_from_slug;
|
use codex_models_manager::model_info::model_info_from_slug;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
@@ -9,7 +10,7 @@ use codex_protocol::openai_models::ModelsResponse;
|
|||||||
use codex_protocol::protocol::COLLABORATION_MODE_CLOSE_TAG;
|
use codex_protocol::protocol::COLLABORATION_MODE_CLOSE_TAG;
|
||||||
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
use core_test_support::responses::ev_response_created;
|
use core_test_support::responses::ev_response_created;
|
||||||
@@ -113,16 +114,10 @@ async fn no_collaboration_instructions_by_default() -> Result<()> {
|
|||||||
let test = test_codex().build(&server).await?;
|
let test = test_codex().build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -173,7 +168,7 @@ async fn catalog_collaboration_messages_track_mode_changes() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_for_model(
|
collaboration_mode: Some(collab_mode_for_model(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
model_slug,
|
model_slug,
|
||||||
@@ -197,7 +192,7 @@ async fn catalog_collaboration_messages_track_mode_changes() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_for_model(
|
collaboration_mode: Some(collab_mode_for_model(
|
||||||
ModeKind::Plan,
|
ModeKind::Plan,
|
||||||
model_slug,
|
model_slug,
|
||||||
@@ -258,7 +253,7 @@ async fn missing_catalog_and_legacy_collaboration_message_clears_prior_instructi
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_for_model(
|
collaboration_mode: Some(collab_mode_for_model(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
model_slug,
|
model_slug,
|
||||||
@@ -272,7 +267,7 @@ async fn missing_catalog_and_legacy_collaboration_message_clears_prior_instructi
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_for_model(
|
collaboration_mode: Some(collab_mode_for_model(
|
||||||
ModeKind::Plan,
|
ModeKind::Plan,
|
||||||
model_slug,
|
model_slug,
|
||||||
@@ -328,7 +323,7 @@ async fn model_change_appends_new_catalog_collaboration_message() -> Result<()>
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_for_model(
|
collaboration_mode: Some(collab_mode_for_model(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
first_slug,
|
first_slug,
|
||||||
@@ -342,7 +337,7 @@ async fn model_change_appends_new_catalog_collaboration_message() -> Result<()>
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(second_slug.to_string()),
|
model: Some(second_slug.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -380,7 +375,7 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu
|
|||||||
let collaboration_mode = collab_mode_with_instructions(Some(collab_text));
|
let collaboration_mode = collab_mode_with_instructions(Some(collab_text));
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -388,16 +383,10 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -425,15 +414,12 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
|
|||||||
let collaboration_mode = collab_mode_with_instructions(Some(collab_text));
|
let collaboration_mode = collab_mode_with_instructions(Some(collab_text));
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
||||||
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
||||||
@@ -444,8 +430,8 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
|
|||||||
),
|
),
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -475,15 +461,12 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> {
|
|||||||
let collaboration_mode = collab_mode_with_instructions(Some("turn instructions"));
|
let collaboration_mode = collab_mode_with_instructions(Some("turn instructions"));
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
||||||
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
||||||
@@ -494,8 +477,8 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> {
|
|||||||
),
|
),
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -526,7 +509,7 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -534,16 +517,10 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -574,7 +551,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(base_mode),
|
collaboration_mode: Some(base_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -582,15 +559,12 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
approval_policy: Some(test.config.permissions.approval_policy.value()),
|
||||||
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
|
||||||
@@ -601,8 +575,8 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
|||||||
),
|
),
|
||||||
collaboration_mode: Some(turn_mode),
|
collaboration_mode: Some(turn_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -638,7 +612,7 @@ async fn collaboration_mode_update_ignores_instruction_changes_within_same_mode(
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_instructions(Some(first_text))),
|
collaboration_mode: Some(collab_mode_with_instructions(Some(first_text))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -646,22 +620,16 @@ async fn collaboration_mode_update_ignores_instruction_changes_within_same_mode(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_instructions(Some(second_text))),
|
collaboration_mode: Some(collab_mode_with_instructions(Some(second_text))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -669,16 +637,10 @@ async fn collaboration_mode_update_ignores_instruction_changes_within_same_mode(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -713,7 +675,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -721,22 +683,16 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -744,16 +700,10 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -787,7 +737,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
Some(default_text),
|
Some(default_text),
|
||||||
@@ -798,22 +748,16 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
||||||
ModeKind::Plan,
|
ModeKind::Plan,
|
||||||
Some(plan_text),
|
Some(plan_text),
|
||||||
@@ -824,16 +768,10 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -868,7 +806,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
Some(collab_text),
|
Some(collab_text),
|
||||||
@@ -879,22 +817,16 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
collaboration_mode: Some(collab_mode_with_mode_and_instructions(
|
||||||
ModeKind::Default,
|
ModeKind::Default,
|
||||||
Some(collab_text),
|
Some(collab_text),
|
||||||
@@ -905,16 +837,10 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -948,7 +874,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
|
|||||||
let collab_text = "resume instructions";
|
let collab_text = "resume instructions";
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&initial.codex,
|
&initial.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -957,32 +883,20 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
let resumed = builder.restart(&server, &initial).await?;
|
let resumed = builder.restart(&server, &initial).await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "after resume".into(),
|
||||||
text: "after resume".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1010,7 +924,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
collaboration_mode: Some(CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: Settings {
|
settings: Settings {
|
||||||
@@ -1025,16 +939,10 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::compact::SUMMARIZATION_PROMPT;
|
use codex_core::compact::SUMMARIZATION_PROMPT;
|
||||||
use codex_core::compact::SUMMARY_PREFIX;
|
use codex_core::compact::SUMMARY_PREFIX;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
@@ -11,6 +12,9 @@ use codex_model_provider_info::ModelProviderInfo;
|
|||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
use codex_protocol::config_types::AutoCompactTokenLimitScope;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::items::TurnItem;
|
use codex_protocol::items::TurnItem;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
@@ -22,6 +26,7 @@ use codex_protocol::protocol::HookRunStatus;
|
|||||||
use codex_protocol::protocol::ItemCompletedEvent;
|
use codex_protocol::protocol::ItemCompletedEvent;
|
||||||
use codex_protocol::protocol::ItemStartedEvent;
|
use codex_protocol::protocol::ItemStartedEvent;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
@@ -102,33 +107,32 @@ fn ev_shell_command_call(call_id: &str, command: &str) -> serde_json::Value {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disabled_permission_user_turn(text: impl Into<String>, cwd: PathBuf, model: String) -> Op {
|
fn disabled_permission_user_turn(
|
||||||
|
text: impl Into<String>,
|
||||||
|
cwd: PathBuf,
|
||||||
|
model: String,
|
||||||
|
) -> TurnInputRequest {
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.into(),
|
||||||
text: text.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
environments: Some(local_selections(cwd.abs())),
|
||||||
responsesapi_client_metadata: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
additional_context: Default::default(),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
permission_profile,
|
||||||
environments: Some(local_selections(cwd.abs())),
|
collaboration_mode: Some(CollaborationMode {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
mode: ModeKind::Default,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
settings: Settings {
|
||||||
permission_profile,
|
model,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
reasoning_effort: None,
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
developer_instructions: None,
|
||||||
settings: codex_protocol::config_types::Settings {
|
},
|
||||||
model,
|
}),
|
||||||
reasoning_effort: None,
|
..Default::default()
|
||||||
developer_instructions: None,
|
})
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auto_summary(summary: &str) -> String {
|
fn auto_summary(summary: &str) -> String {
|
||||||
@@ -528,16 +532,10 @@ async fn summarize_context_three_requests_and_instructions() {
|
|||||||
|
|
||||||
// 1) Normal user input – should hit server once.
|
// 1) Normal user input – should hit server once.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello world".into(),
|
||||||
text: "hello world".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -553,16 +551,10 @@ async fn summarize_context_three_requests_and_instructions() {
|
|||||||
|
|
||||||
// 3) Next user input – third hit; history should include only the summary.
|
// 3) Next user input – third hit; history should include only the summary.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: THIRD_USER_MSG.into(),
|
||||||
text: THIRD_USER_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -723,16 +715,10 @@ async fn manual_pre_compact_block_decision_does_not_block_compaction() {
|
|||||||
let codex = test.codex.clone();
|
let codex = test.codex.clone();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello before blocked compact".to_string(),
|
||||||
text: "hello before blocked compact".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit first user turn");
|
.expect("submit first user turn");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -796,16 +782,10 @@ async fn compact_hooks_respect_matchers_and_post_runs_after_compaction() {
|
|||||||
let codex = test.codex.clone();
|
let codex = test.codex.clone();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello before matched compact".to_string(),
|
||||||
text: "hello before matched compact".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit first user turn");
|
.expect("submit first user turn");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -866,16 +846,10 @@ async fn manual_compact_uses_custom_prompt() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "USER_ONE".to_string(),
|
||||||
text: "USER_ONE".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit first user turn");
|
.expect("submit first user turn");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1013,16 +987,10 @@ async fn manual_compact_emits_context_compaction_items() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "manual compact".into(),
|
||||||
text: "manual compact".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1179,16 +1147,10 @@ async fn multiple_auto_compact_per_task_runs_after_token_limit_hit() {
|
|||||||
|
|
||||||
// Start the conversation with the user message
|
// Start the conversation with the user message
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user_message.into(),
|
||||||
text: user_message.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user input");
|
.expect("submit user input");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1654,48 +1616,30 @@ async fn auto_compact_runs_after_token_limit_hit() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: FIRST_AUTO_MSG.into(),
|
||||||
text: FIRST_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: SECOND_AUTO_MSG.into(),
|
||||||
text: SECOND_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: POST_AUTO_USER_MSG.into(),
|
||||||
text: POST_AUTO_USER_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1857,16 +1801,10 @@ async fn auto_compact_emits_context_compaction_items() {
|
|||||||
|
|
||||||
for user in [FIRST_AUTO_MSG, SECOND_AUTO_MSG, POST_AUTO_USER_MSG] {
|
for user in [FIRST_AUTO_MSG, SECOND_AUTO_MSG, POST_AUTO_USER_MSG] {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user.into(),
|
||||||
text: user.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1938,46 +1876,28 @@ async fn auto_compact_starts_after_turn_started() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: FIRST_AUTO_MSG.into(),
|
||||||
text: FIRST_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: SECOND_AUTO_MSG.into(),
|
||||||
text: SECOND_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: POST_AUTO_USER_MSG.into(),
|
||||||
text: POST_AUTO_USER_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2079,7 +1999,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
follow_up_user,
|
follow_up_user,
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
resumed.session_configured.model.clone(),
|
resumed.session_configured.model.clone(),
|
||||||
@@ -2158,7 +2078,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -2171,7 +2091,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -2260,7 +2180,7 @@ async fn pre_sampling_compact_runs_when_comp_hash_changes() {
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -2273,7 +2193,7 @@ async fn pre_sampling_compact_runs_when_comp_hash_changes() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -2368,7 +2288,7 @@ async fn pre_sampling_compact_falls_back_from_retired_previous_model_after_renam
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
initial.cwd.path().to_path_buf(),
|
initial.cwd.path().to_path_buf(),
|
||||||
retired_model.to_string(),
|
retired_model.to_string(),
|
||||||
@@ -2405,7 +2325,7 @@ async fn pre_sampling_compact_falls_back_from_retired_previous_model_after_renam
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
renamed_model.to_string(),
|
renamed_model.to_string(),
|
||||||
@@ -2507,7 +2427,7 @@ async fn pre_sampling_compact_falls_back_when_previous_model_is_not_found() {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
initial.cwd.path().to_path_buf(),
|
initial.cwd.path().to_path_buf(),
|
||||||
retired_model.to_string(),
|
retired_model.to_string(),
|
||||||
@@ -2545,7 +2465,7 @@ async fn pre_sampling_compact_falls_back_when_previous_model_is_not_found() {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
renamed_model.to_string(),
|
renamed_model.to_string(),
|
||||||
@@ -2640,7 +2560,7 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
retired_model.to_string(),
|
retired_model.to_string(),
|
||||||
@@ -2653,7 +2573,7 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -2745,7 +2665,7 @@ async fn pre_sampling_legacy_remote_compact_falls_back_after_previous_model_inva
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
retired_model.to_string(),
|
retired_model.to_string(),
|
||||||
@@ -2758,7 +2678,7 @@ async fn pre_sampling_legacy_remote_compact_falls_back_after_previous_model_inva
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -2835,7 +2755,7 @@ async fn pre_sampling_compact_keeps_unknown_previous_model_for_api_key_auth_and_
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -2848,7 +2768,7 @@ async fn pre_sampling_compact_keeps_unknown_previous_model_for_api_key_auth_and_
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -2925,7 +2845,7 @@ async fn pre_sampling_compact_skips_when_either_comp_hash_is_missing() {
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before hash",
|
"before hash",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
model_without_hash.to_string(),
|
model_without_hash.to_string(),
|
||||||
@@ -2938,7 +2858,7 @@ async fn pre_sampling_compact_skips_when_either_comp_hash_is_missing() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"hash introduced",
|
"hash introduced",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
model_with_hash.to_string(),
|
model_with_hash.to_string(),
|
||||||
@@ -2951,7 +2871,7 @@ async fn pre_sampling_compact_skips_when_either_comp_hash_is_missing() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"hash removed",
|
"hash removed",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model_without_hash.to_string(),
|
next_model_without_hash.to_string(),
|
||||||
@@ -3034,7 +2954,7 @@ async fn body_after_prefix_model_switch_budget_compacts_with_next_model() {
|
|||||||
let test = builder.build(&server).await.expect("build test codex");
|
let test = builder.build(&server).await.expect("build test codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before switch",
|
"before switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -3047,7 +2967,7 @@ async fn body_after_prefix_model_switch_budget_compacts_with_next_model() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after switch",
|
"after switch",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -3134,7 +3054,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before resume",
|
"before resume",
|
||||||
initial.cwd.path().to_path_buf(),
|
initial.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -3171,7 +3091,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after resume",
|
"after resume",
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -3255,7 +3175,7 @@ async fn pre_sampling_compact_recovers_comp_hash_after_resume() {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before resume",
|
"before resume",
|
||||||
initial.cwd.path().to_path_buf(),
|
initial.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -3302,7 +3222,7 @@ async fn pre_sampling_compact_recovers_comp_hash_after_resume() {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after resume",
|
"after resume",
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -3382,7 +3302,7 @@ async fn pre_sampling_compact_skips_missing_comp_hash_after_resume() {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"before resume",
|
"before resume",
|
||||||
initial.cwd.path().to_path_buf(),
|
initial.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -3427,7 +3347,7 @@ async fn pre_sampling_compact_skips_missing_comp_hash_after_resume() {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"after resume",
|
"after resume",
|
||||||
resumed.cwd.path().to_path_buf(),
|
resumed.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -3522,46 +3442,28 @@ async fn auto_compact_persists_rollout_entries() {
|
|||||||
let session_configured = test.session_configured;
|
let session_configured = test.session_configured;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: FIRST_AUTO_MSG.into(),
|
||||||
text: FIRST_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: SECOND_AUTO_MSG.into(),
|
||||||
text: SECOND_AUTO_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: POST_AUTO_USER_MSG.into(),
|
||||||
text: POST_AUTO_USER_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -3636,16 +3538,10 @@ async fn manual_compact_retries_after_context_window_error() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first turn".into(),
|
||||||
text: "first turn".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -3740,16 +3636,10 @@ async fn manual_compact_non_context_failure_retries_then_emits_task_error() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first turn".into(),
|
||||||
text: "first turn".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user input");
|
.expect("submit user input");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -3835,16 +3725,10 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: first_user_message.into(),
|
||||||
text: first_user_message.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -3853,16 +3737,10 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
|||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: second_user_message.into(),
|
||||||
text: second_user_message.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -3871,16 +3749,10 @@ async fn manual_compact_twice_preserves_latest_user_messages() {
|
|||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: final_user_message.into(),
|
||||||
text: final_user_message.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -4079,16 +3951,10 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
|
|||||||
let mut auto_compact_lifecycle_events = Vec::new();
|
let mut auto_compact_lifecycle_events = Vec::new();
|
||||||
for user in [MULTI_AUTO_MSG, follow_up_user, final_user] {
|
for user in [MULTI_AUTO_MSG, follow_up_user, final_user] {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user.into(),
|
||||||
text: user.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -4184,16 +4050,10 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
|
|||||||
let codex = builder.build(&server).await.unwrap().codex;
|
let codex = builder.build(&server).await.unwrap().codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: FUNCTION_CALL_LIMIT_MSG.into(),
|
||||||
text: FUNCTION_CALL_LIMIT_MSG.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -4619,16 +4479,10 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user.into(),
|
||||||
text: user.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -4742,16 +4596,10 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
|||||||
|
|
||||||
for user in [first_user, second_user, third_user] {
|
for user in [first_user, second_user, third_user] {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user.into(),
|
||||||
text: user.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -4804,23 +4652,17 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
|||||||
|
|
||||||
for user in ["USER_ONE", "USER_TWO"] {
|
for user in ["USER_ONE", "USER_TWO"] {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user.to_string(),
|
||||||
text: user.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user input");
|
.expect("submit user input");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
}
|
}
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
environments: Some(local_selections(
|
environments: Some(local_selections(
|
||||||
test_path_buf(PRETURN_CONTEXT_DIFF_CWD).abs(),
|
test_path_buf(PRETURN_CONTEXT_DIFF_CWD).abs(),
|
||||||
)),
|
)),
|
||||||
@@ -4832,22 +4674,16 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
|||||||
let image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="
|
let image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="
|
||||||
.to_string();
|
.to_string();
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![
|
UserInput::Image {
|
||||||
UserInput::Image {
|
image_url: image_url.clone(),
|
||||||
image_url: image_url.clone(),
|
detail: None,
|
||||||
detail: None,
|
},
|
||||||
},
|
UserInput::Text {
|
||||||
UserInput::Text {
|
text: "USER_THREE".to_string(),
|
||||||
text: "USER_THREE".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
},
|
||||||
},
|
]))
|
||||||
],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user input");
|
.expect("submit user input");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -4930,7 +4766,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
|
|||||||
.expect("build codex");
|
.expect("build codex");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"BEFORE_SWITCH_USER",
|
"BEFORE_SWITCH_USER",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
previous_model.to_string(),
|
previous_model.to_string(),
|
||||||
@@ -4943,7 +4779,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_permission_user_turn(
|
.start_or_steer_turn(disabled_permission_user_turn(
|
||||||
"AFTER_SWITCH_USER",
|
"AFTER_SWITCH_USER",
|
||||||
test.cwd.path().to_path_buf(),
|
test.cwd.path().to_path_buf(),
|
||||||
next_model.to_string(),
|
next_model.to_string(),
|
||||||
@@ -5027,31 +4863,19 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "USER_ONE".to_string(),
|
||||||
text: "USER_ONE".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit first user");
|
.expect("submit first user");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "USER_TWO".to_string(),
|
||||||
text: "USER_TWO".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit second user");
|
.expect("submit second user");
|
||||||
let error_message = wait_for_event_match(&codex, |event| match event {
|
let error_message = wait_for_event_match(&codex, |event| match event {
|
||||||
@@ -5115,16 +4939,10 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
|
|||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "AFTER_MANUAL_EMPTY_COMPACT".to_string(),
|
||||||
text: "AFTER_MANUAL_EMPTY_COMPACT".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit follow-up user input");
|
.expect("submit follow-up user input");
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -605,13 +606,7 @@ async fn capture_from_requests(
|
|||||||
|
|
||||||
async fn submit_user_input(codex: &codex_core::CodexThread, items: Vec<UserInput>) -> Result<()> {
|
async fn submit_user_input(codex: &codex_core::CodexThread, items: Vec<UserInput>) -> Result<()> {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(items))
|
||||||
items,
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_turn_complete(codex).await;
|
wait_for_turn_complete(codex).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_core::CodexThread;
|
use codex_core::CodexThread;
|
||||||
use codex_core::ThreadManager;
|
use codex_core::ThreadManager;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::compact::SUMMARIZATION_PROMPT;
|
use codex_core::compact::SUMMARIZATION_PROMPT;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||||
@@ -26,6 +27,7 @@ use codex_protocol::models::ContentItem;
|
|||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::context_snapshot;
|
use core_test_support::context_snapshot;
|
||||||
@@ -636,7 +638,7 @@ async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> {
|
|||||||
std::fs::create_dir_all(&override_cwd)?;
|
std::fs::create_dir_all(&override_cwd)?;
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&conversation,
|
&conversation,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
environments: Some(local_selections(override_cwd.clone())),
|
environments: Some(local_selections(override_cwd.clone())),
|
||||||
collaboration_mode: Some(CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
@@ -859,16 +861,10 @@ async fn start_test_conversation(
|
|||||||
|
|
||||||
async fn user_turn(conversation: &Arc<CodexThread>, text: &str) {
|
async fn user_turn(conversation: &Arc<CodexThread>, text: &str) {
|
||||||
conversation
|
conversation
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.into(),
|
||||||
text: text.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user turn");
|
.expect("submit user turn");
|
||||||
wait_for_event(conversation, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(conversation, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use chrono::Utc;
|
|||||||
use codex_core::SleepFuture;
|
use codex_core::SleepFuture;
|
||||||
use codex_core::TimeFuture;
|
use codex_core::TimeFuture;
|
||||||
use codex_core::TimeProvider;
|
use codex_core::TimeProvider;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::CurrentTimeReminderConfig;
|
use codex_core::config::CurrentTimeReminderConfig;
|
||||||
use codex_features::CurrentTimeReminderDeliveryMode;
|
use codex_features::CurrentTimeReminderDeliveryMode;
|
||||||
use codex_features::CurrentTimeSource;
|
use codex_features::CurrentTimeSource;
|
||||||
@@ -428,16 +429,10 @@ async fn time_provider_failure_stops_before_inference() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "fail before inference".into(),
|
||||||
text: "fail before inference".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::Error(error) =
|
let EventMsg::Error(error) =
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::sandboxing::SandboxPermissions;
|
use codex_core::sandboxing::SandboxPermissions;
|
||||||
@@ -260,20 +261,17 @@ async fn cyber_model_user_approval_never_offers_a_reusable_prefix(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run a command with one-time approval".to_string(),
|
text: "run a command with one-time approval".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::ExecApprovalRequest(approval) = wait_for_event(&test.codex, |event| {
|
let EventMsg::ExecApprovalRequest(approval) = wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::test_support::CloudConfigBundleFixture;
|
use codex_config::test_support::CloudConfigBundleFixture;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
@@ -12,6 +13,7 @@ use codex_protocol::protocol::EventMsg;
|
|||||||
use codex_protocol::protocol::GranularApprovalConfig;
|
use codex_protocol::protocol::GranularApprovalConfig;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_assistant_message;
|
use core_test_support::responses::ev_assistant_message;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -53,23 +55,20 @@ async fn submit_user_turn(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: collaboration_mode.or({
|
collaboration_mode: collaboration_mode.or({
|
||||||
Some(codex_protocol::config_types::CollaborationMode {
|
Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
@@ -77,8 +76,8 @@ async fn submit_user_turn(
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -402,30 +401,27 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run shell command".into(),
|
text: "run shell command".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::ExecCommandEnd(end) = wait_for_event(&test.codex, |event| {
|
let EventMsg::ExecCommandEnd(end) = wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_extension_api::ExtensionRegistry;
|
use codex_extension_api::ExtensionRegistry;
|
||||||
@@ -10,6 +11,9 @@ use codex_features::Feature;
|
|||||||
use codex_image_generation_extension::install as install_image_generation_extension;
|
use codex_image_generation_extension::install as install_image_generation_extension;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::config_types::WebSearchMode;
|
use codex_protocol::config_types::WebSearchMode;
|
||||||
use codex_protocol::models::FileSystemPermissions;
|
use codex_protocol::models::FileSystemPermissions;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
@@ -22,6 +26,7 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -244,31 +249,28 @@ async fn extension_tool_uses_granted_turn_permissions_without_local_persistence(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(base_permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(base_permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "request access and edit the image".to_string(),
|
text: "request access and edit the image".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let event = wait_for_event(&test.codex, |event| {
|
let event = wait_for_event(&test.codex, |event| {
|
||||||
matches!(
|
matches!(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
use codex_core::ForkSnapshot;
|
use codex_core::ForkSnapshot;
|
||||||
use codex_core::NewThread;
|
use codex_core::NewThread;
|
||||||
use codex_core::ThreadConfigSnapshot;
|
use codex_core::ThreadConfigSnapshot;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::parse_turn_item;
|
use codex_core::parse_turn_item;
|
||||||
use codex_history::InitialHistory;
|
use codex_history::InitialHistory;
|
||||||
use codex_history::ResumedHistory;
|
use codex_history::ResumedHistory;
|
||||||
@@ -11,7 +12,6 @@ use codex_history::RolloutLine;
|
|||||||
use codex_protocol::items::TurnItem;
|
use codex_protocol::items::TurnItem;
|
||||||
use codex_protocol::mcp::ClientMcpExtensions;
|
use codex_protocol::mcp::ClientMcpExtensions;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadHistoryMode;
|
use codex_protocol::protocol::ThreadHistoryMode;
|
||||||
use codex_protocol::protocol::ThreadSettingsAppliedEvent;
|
use codex_protocol::protocol::ThreadSettingsAppliedEvent;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -55,16 +55,10 @@ async fn fork_thread_twice_drops_to_first_message() {
|
|||||||
// Send three user messages; wait for three completed turns.
|
// Send three user messages; wait for three completed turns.
|
||||||
for text in ["first", "second", "third"] {
|
for text in ["first", "second", "third"] {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _ = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
let _ = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -204,16 +198,10 @@ async fn assert_copied_fork_persists_inherited_history(history_mode: ThreadHisto
|
|||||||
let thread_manager = test.thread_manager.clone();
|
let thread_manager = test.thread_manager.clone();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "fork me from stored history".to_string(),
|
||||||
text: "fork me from stored history".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit initial user turn");
|
.expect("submit initial user turn");
|
||||||
let _ = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
let _ = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -281,16 +269,10 @@ async fn assert_copied_fork_persists_inherited_history(history_mode: ThreadHisto
|
|||||||
.expect("resume copied paginated fork")
|
.expect("resume copied paginated fork")
|
||||||
.thread;
|
.thread;
|
||||||
resumed
|
resumed
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "continue after cold resume".to_string(),
|
||||||
text: "continue after cold resume".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("start resumed turn");
|
.expect("start resumed turn");
|
||||||
wait_for_event(&resumed, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&resumed, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use anyhow::ensure;
|
use anyhow::ensure;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@@ -11,7 +12,6 @@ use codex_protocol::config_types::ApprovalsReviewer;
|
|||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSource;
|
use codex_protocol::protocol::ThreadSource;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::PathBufExt;
|
use core_test_support::PathBufExt;
|
||||||
@@ -196,13 +196,10 @@ async fn guardian_prewarm_and_review_skip_redundant_git_enrichment() -> Result<(
|
|||||||
|
|
||||||
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
vec![UserInput::Text {
|
text: "run a command that requires Guardian review".into(),
|
||||||
text: "run a command that requires Guardian review".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}]
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
let (user_turn, guardian_turn) = tokio::time::timeout(Duration::from_secs(5), async {
|
let (user_turn, guardian_turn) = tokio::time::timeout(Duration::from_secs(5), async {
|
||||||
tokio::join!(
|
tokio::join!(
|
||||||
@@ -295,16 +292,10 @@ async fn ephemeral_system_thread_prewarm_skips_and_turn_observes_fresh_state(
|
|||||||
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
||||||
system_thread
|
system_thread
|
||||||
.thread
|
.thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "generate a thread title".into(),
|
||||||
text: "generate a thread title".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(system_thread.thread.as_ref(), |event| {
|
wait_for_event(system_thread.thread.as_ref(), |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -423,24 +414,21 @@ async fn concurrent_turns_keep_distinct_worktree_and_repository_metadata() -> Re
|
|||||||
|
|
||||||
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
std::fs::write(repo.path().join("untracked.txt"), "dirty\n")?;
|
||||||
std::fs::write(other_repo.path().join("untracked.txt"), "dirty\n")?;
|
std::fs::write(other_repo.path().join("untracked.txt"), "dirty\n")?;
|
||||||
let user_turn = |prompt: &str| Op::UserInput {
|
let user_turn = |prompt: &str| {
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.to_string(),
|
text: prompt.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
};
|
};
|
||||||
tokio::try_join!(
|
tokio::try_join!(
|
||||||
test.codex.submit(user_turn("inspect the main worktree")),
|
test.codex
|
||||||
|
.start_or_steer_turn(user_turn("inspect the main worktree")),
|
||||||
worktree_thread
|
worktree_thread
|
||||||
.thread
|
.thread
|
||||||
.submit(user_turn("inspect the linked worktree")),
|
.start_or_steer_turn(user_turn("inspect the linked worktree")),
|
||||||
other_thread
|
other_thread
|
||||||
.thread
|
.thread
|
||||||
.submit(user_turn("inspect the other repository"))
|
.start_or_steer_turn(user_turn("inspect the other repository"))
|
||||||
)?;
|
)?;
|
||||||
tokio::join!(
|
tokio::join!(
|
||||||
wait_for_event(test.codex.as_ref(), |event| matches!(
|
wait_for_event(test.codex.as_ref(), |event| matches!(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use chrono::Utc;
|
|||||||
use codex_core::SleepFuture;
|
use codex_core::SleepFuture;
|
||||||
use codex_core::TimeFuture;
|
use codex_core::TimeFuture;
|
||||||
use codex_core::TimeProvider;
|
use codex_core::TimeProvider;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::config::CurrentTimeReminderConfig;
|
use codex_core::config::CurrentTimeReminderConfig;
|
||||||
use codex_core::sandboxing::SandboxPermissions;
|
use codex_core::sandboxing::SandboxPermissions;
|
||||||
@@ -24,6 +25,7 @@ use codex_protocol::protocol::AskForApproval;
|
|||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::SandboxPolicy;
|
use codex_protocol::protocol::SandboxPolicy;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::TurnAbortReason;
|
use codex_protocol::protocol::TurnAbortReason;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::fs_wait;
|
use core_test_support::fs_wait;
|
||||||
@@ -258,13 +260,10 @@ async fn guardian_session_prewarms_and_is_reused_for_first_review(
|
|||||||
.expect("guardian thread id");
|
.expect("guardian thread id");
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
vec![UserInput::Text {
|
text: "run a command that requires Guardian review".into(),
|
||||||
text: "run a command that requires Guardian review".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}]
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
let guardian_review = tokio::time::timeout(
|
let guardian_review = tokio::time::timeout(
|
||||||
Duration::from_secs(5),
|
Duration::from_secs(5),
|
||||||
@@ -448,22 +447,19 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run two commands that require Guardian review".into(),
|
text: "run two commands that require Guardian review".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -637,22 +633,19 @@ async fn interrupted_guardian_tool_review_aborts_without_executing_the_command()
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "interrupt a Guardian-reviewed command".into(),
|
text: "interrupt a Guardian-reviewed command".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
tokio::time::timeout(Duration::from_secs(5), async {
|
tokio::time::timeout(Duration::from_secs(5), async {
|
||||||
@@ -684,13 +677,10 @@ async fn interrupted_guardian_tool_review_aborts_without_executing_the_command()
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
vec![UserInput::Text {
|
text: "verify Guardian cancellation left the next turn clean".into(),
|
||||||
text: "verify Guardian cancellation left the next turn clean".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}]
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -784,21 +774,18 @@ async fn guardian_denial_rejects_tool_call_with_rationale() -> Result<()> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run a command that Guardian should deny".into(),
|
text: "run a command that Guardian should deny".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -898,21 +885,18 @@ async fn cyber_model_guardian_denial_interrupts_turn_immediately() -> Result<()>
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run a command that Guardian should deny for a cyber model".into(),
|
text: "run a command that Guardian should deny for a cyber model".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let warning = wait_for_event(&test.codex, |event| {
|
let warning = wait_for_event(&test.codex, |event| {
|
||||||
@@ -1030,22 +1014,19 @@ printf '%s\n' "${@: -1}" >> "${payload_path}""#,
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run a command that requires Guardian review".into(),
|
text: "run a command that requires Guardian review".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::path::Path;
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::config::ThreadStoreConfig;
|
use codex_core::config::ThreadStoreConfig;
|
||||||
@@ -25,6 +26,7 @@ use codex_protocol::protocol::EventMsg;
|
|||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::SubAgentSource;
|
use codex_protocol::protocol::SubAgentSource;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -1613,21 +1615,20 @@ async fn async_hook_context_is_injected_into_the_active_turn() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "observe async context immediately".to_string(),
|
text: "observe async context immediately".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(
|
||||||
responsesapi_client_metadata: None,
|
codex_protocol::protocol::ThreadSettingsOverrides {
|
||||||
additional_context: Default::default(),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
sandbox_policy: Some(sandbox_policy),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
permission_profile,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
..Default::default()
|
||||||
permission_profile,
|
},
|
||||||
..Default::default()
|
),
|
||||||
},
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let finished_path = test
|
let finished_path = test
|
||||||
@@ -1753,16 +1754,10 @@ async fn async_hook_finishing_while_idle_waits_for_the_next_turn() -> Result<()>
|
|||||||
|
|
||||||
let next_prompt = "observe the buffered async context";
|
let next_prompt = "observe the buffered async context";
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: next_prompt.to_string(),
|
||||||
text: next_prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut warning_event = None;
|
let mut warning_event = None;
|
||||||
@@ -2636,16 +2631,10 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu
|
|||||||
let test = builder.build_with_streaming_server(&server).await?;
|
let test = builder.build_with_streaming_server(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "initial prompt".to_string(),
|
||||||
text: "initial prompt".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
@@ -2655,16 +2644,10 @@ async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Resu
|
|||||||
|
|
||||||
for text in ["accepted queued prompt", "blocked queued prompt"] {
|
for text in ["accepted queued prompt", "blocked queued prompt"] {
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2906,21 +2889,18 @@ async fn permission_request_hook_allow_bypasses_strict_auto_review() -> Result<(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "request strict review, then run the shell command".into(),
|
text: "request strict review, then run the shell command".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let request = wait_for_event(&test.codex, |event| {
|
let request = wait_for_event(&test.codex, |event| {
|
||||||
@@ -3595,21 +3575,20 @@ Path(r"{hook_finished_path}").write_text("finished", encoding="utf-8")
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run the original command with async pre-tool hooks".to_string(),
|
text: "run the original command with async pre-tool hooks".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(
|
||||||
responsesapi_client_metadata: None,
|
codex_protocol::protocol::ThreadSettingsOverrides {
|
||||||
additional_context: Default::default(),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
sandbox_policy: Some(sandbox_policy),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
permission_profile,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
..Default::default()
|
||||||
permission_profile,
|
},
|
||||||
..Default::default()
|
),
|
||||||
},
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
fs_wait::wait_for_path_exists(hook_finished_path, Duration::from_secs(5))
|
fs_wait::wait_for_path_exists(hook_finished_path, Duration::from_secs(5))
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
use codex_history::RolloutLine;
|
use codex_history::RolloutLine;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
|
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
|
||||||
use codex_protocol::models::ImageDetail;
|
use codex_protocol::models::ImageDetail;
|
||||||
@@ -13,6 +17,7 @@ use codex_protocol::models::ResponseItem;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_image::data_url_from_bytes;
|
use codex_utils_image::data_url_from_bytes;
|
||||||
use core_test_support::TempDirExt;
|
use core_test_support::TempDirExt;
|
||||||
@@ -125,8 +130,8 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![
|
TurnInputRequest::user_input(vec![
|
||||||
UserInput::LocalImage {
|
UserInput::LocalImage {
|
||||||
path: abs_path.clone(),
|
path: abs_path.clone(),
|
||||||
detail: None,
|
detail: None,
|
||||||
@@ -135,26 +140,23 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
|
|||||||
text: "pasted image".to_string(),
|
text: "pasted image".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
},
|
},
|
||||||
],
|
])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.abs())),
|
environments: Some(local_selections(cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -224,8 +226,8 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![
|
TurnInputRequest::user_input(vec![
|
||||||
UserInput::Image {
|
UserInput::Image {
|
||||||
image_url: image_url.clone(),
|
image_url: image_url.clone(),
|
||||||
detail: None,
|
detail: None,
|
||||||
@@ -234,26 +236,23 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
|
|||||||
text: "dropped image".to_string(),
|
text: "dropped image".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
},
|
},
|
||||||
],
|
])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.abs())),
|
environments: Some(local_selections(cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -374,16 +373,10 @@ async fn resumed_history_only_emits_resize_notices_for_new_images() -> anyhow::R
|
|||||||
.await;
|
.await;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Image {
|
||||||
items: vec![UserInput::Image {
|
image_url: original_image_url.clone(),
|
||||||
image_url: original_image_url.clone(),
|
detail: Some(ImageDetail::High),
|
||||||
detail: Some(ImageDetail::High),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
@@ -22,7 +23,6 @@ use codex_protocol::error::Result as CoreResult;
|
|||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
use codex_protocol::openai_models::ModelVisibility;
|
use codex_protocol::openai_models::ModelVisibility;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
@@ -184,16 +184,10 @@ async fn run_agent_with_model(models_manager: SharedModelsManager, model_slug: &
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".to_string(),
|
||||||
text: "hello".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
loop {
|
loop {
|
||||||
if matches!(
|
if matches!(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![cfg(not(target_os = "windows"))]
|
#![cfg(not(target_os = "windows"))]
|
||||||
|
|
||||||
use anyhow::Ok;
|
use anyhow::Ok;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
use codex_history::RolloutLine;
|
use codex_history::RolloutLine;
|
||||||
@@ -15,8 +16,8 @@ use codex_protocol::protocol::AskForApproval;
|
|||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ItemCompletedEvent;
|
use codex_protocol::protocol::ItemCompletedEvent;
|
||||||
use codex_protocol::protocol::ItemStartedEvent;
|
use codex_protocol::protocol::ItemStartedEvent;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadHistoryMode;
|
use codex_protocol::protocol::ThreadHistoryMode;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::ByteRange;
|
use codex_protocol::user_input::ByteRange;
|
||||||
use codex_protocol::user_input::TextElement;
|
use codex_protocol::user_input::TextElement;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -48,27 +49,22 @@ fn disabled_plan_turn(
|
|||||||
text: &str,
|
text: &str,
|
||||||
_model: String,
|
_model: String,
|
||||||
collaboration_mode: CollaborationMode,
|
collaboration_mode: CollaborationMode,
|
||||||
) -> anyhow::Result<Op> {
|
) -> anyhow::Result<TurnInputRequest> {
|
||||||
let cwd = std::env::current_dir()?.abs();
|
let cwd = std::env::current_dir()?.abs();
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
Ok(Op::UserInput {
|
Ok(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.into(),
|
||||||
text: text.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
environments: Some(local_selections(cwd)),
|
||||||
responsesapi_client_metadata: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
additional_context: Default::default(),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
permission_profile,
|
||||||
environments: Some(local_selections(cwd)),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
..Default::default()
|
||||||
sandbox_policy: Some(sandbox_policy),
|
}))
|
||||||
permission_profile,
|
|
||||||
collaboration_mode: Some(collaboration_mode),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -92,13 +88,7 @@ async fn user_message_item_is_emitted() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![expected_input.clone()]))
|
||||||
items: vec![expected_input.clone()],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let started_item = wait_for_event_match(&codex, |ev| match ev {
|
let started_item = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -148,16 +138,10 @@ async fn assistant_message_item_is_emitted() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, first_response).await;
|
mount_sse_once(&server, first_response).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "please summarize results".into(),
|
||||||
text: "please summarize results".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let started = wait_for_event_match(&codex, |ev| match ev {
|
let started = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -209,16 +193,10 @@ async fn reasoning_item_is_emitted() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, first_response).await;
|
mount_sse_once(&server, first_response).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "explain your reasoning".into(),
|
||||||
text: "explain your reasoning".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let started = wait_for_event_match(&codex, |ev| match ev {
|
let started = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -280,16 +258,10 @@ async fn missing_streamed_reasoning_id_is_reused_for_completion() -> anyhow::Res
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "explain your reasoning".into(),
|
||||||
text: "explain your reasoning".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let started_id = wait_for_event_match(&codex, |ev| match ev {
|
let started_id = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -339,16 +311,10 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, first_response).await;
|
mount_sse_once(&server, first_response).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "find the weather".into(),
|
||||||
text: "find the weather".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let started = wait_for_event_match(&codex, |ev| match ev {
|
let started = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -434,16 +400,10 @@ async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, stream).await;
|
mount_sse_once(&server, stream).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "please stream text".into(),
|
||||||
text: "please stream text".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (started_turn_id, started_item) = wait_for_event_match(&codex, |ev| match ev {
|
let (started_turn_id, started_item) = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -513,7 +473,7 @@ async fn plan_mode_emits_plan_item_from_proposed_plan_block() -> anyhow::Result<
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(disabled_plan_turn(
|
.start_or_steer_turn(disabled_plan_turn(
|
||||||
"please plan",
|
"please plan",
|
||||||
session_configured.model.clone(),
|
session_configured.model.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
@@ -578,7 +538,7 @@ async fn plan_mode_strips_plan_from_agent_messages() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(disabled_plan_turn(
|
.start_or_steer_turn(disabled_plan_turn(
|
||||||
"please plan",
|
"please plan",
|
||||||
session_configured.model.clone(),
|
session_configured.model.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
@@ -675,7 +635,7 @@ async fn plan_mode_streaming_citations_are_stripped_across_added_deltas_and_done
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(disabled_plan_turn(
|
.start_or_steer_turn(disabled_plan_turn(
|
||||||
"please plan with citations",
|
"please plan with citations",
|
||||||
session_configured.model.clone(),
|
session_configured.model.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
@@ -850,7 +810,7 @@ async fn plan_mode_streaming_proposed_plan_tag_split_across_added_and_delta_is_p
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(disabled_plan_turn(
|
.start_or_steer_turn(disabled_plan_turn(
|
||||||
"please plan",
|
"please plan",
|
||||||
session_configured.model.clone(),
|
session_configured.model.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
@@ -952,7 +912,7 @@ async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(disabled_plan_turn(
|
.start_or_steer_turn(disabled_plan_turn(
|
||||||
"please plan",
|
"please plan",
|
||||||
session_configured.model.clone(),
|
session_configured.model.clone(),
|
||||||
collaboration_mode,
|
collaboration_mode,
|
||||||
@@ -1018,16 +978,10 @@ async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, stream).await;
|
mount_sse_once(&server, stream).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "reason through it".into(),
|
||||||
text: "reason through it".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -1095,16 +1049,10 @@ async fn sequential_cutoff_renders_done_summaries_for_active_reasoning_item() ->
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "reason through it".into(),
|
||||||
text: "reason through it".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -1165,16 +1113,10 @@ async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> {
|
|||||||
mount_sse_once(&server, stream).await;
|
mount_sse_once(&server, stream).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "show raw reasoning".into(),
|
||||||
text: "show raw reasoning".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
#![cfg(not(target_os = "windows"))]
|
#![cfg(not(target_os = "windows"))]
|
||||||
|
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
|
use codex_core::TurnStartOptions;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
use core_test_support::skip_if_no_network;
|
use core_test_support::skip_if_no_network;
|
||||||
@@ -77,30 +82,31 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// 1) Normal user input – should hit server once.
|
// 1) Normal user input – should hit server once.
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello world".into(),
|
text: "hello world".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: Some(serde_json::from_str(SCHEMA)?),
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model,
|
model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
})
|
||||||
})
|
.on_start(TurnStartOptions {
|
||||||
|
final_output_json_schema: Some(serde_json::from_str(SCHEMA)?),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let message = wait_for_event(&codex, |ev| matches!(ev, EventMsg::AgentMessage(_))).await;
|
let message = wait_for_event(&codex, |ev| matches!(ev, EventMsg::AgentMessage(_))).await;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||||
use codex_protocol::approvals::ElicitationRequest;
|
use codex_protocol::approvals::ElicitationRequest;
|
||||||
@@ -135,16 +136,10 @@ default_tools_approval_mode = "auto"
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec to create a calendar event.".to_string(),
|
||||||
text: "Use [$calendar](app://calendar) to create a calendar event.".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::ElicitationRequest(request) = wait_for_event(&test.codex, |event| {
|
let EventMsg::ElicitationRequest(request) = wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ use codex_features::Feature;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
use core_test_support::apps_test_server::AppsTestServer;
|
use core_test_support::apps_test_server::AppsTestServer;
|
||||||
@@ -313,8 +314,8 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()>
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, fixture.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, fixture.config.cwd.as_path());
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![
|
TurnInputRequest::user_input(vec![
|
||||||
UserInput::Text {
|
UserInput::Text {
|
||||||
text: "please use $proxy-skill".to_string(),
|
text: "please use $proxy-skill".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
@@ -323,18 +324,15 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()>
|
|||||||
name: "proxy-skill".to_string(),
|
name: "proxy-skill".to_string(),
|
||||||
path: skill_path.to_path_buf(),
|
path: skill_path.to_path_buf(),
|
||||||
},
|
},
|
||||||
],
|
])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(fixture.config.cwd.clone())),
|
environments: Some(local_selections(fixture.config.cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
core_test_support::wait_for_event(fixture.codex.as_ref(), |event| {
|
core_test_support::wait_for_event(fixture.codex.as_ref(), |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use codex_config::types::McpServerConfig;
|
|||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_core::NewThread;
|
use codex_core::NewThread;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_exec_server::ExecutorFileSystem;
|
use codex_exec_server::ExecutorFileSystem;
|
||||||
use codex_exec_server::RemoveOptions;
|
use codex_exec_server::RemoveOptions;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
@@ -17,6 +18,7 @@ use codex_protocol::protocol::McpStartupStatus;
|
|||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::SubAgentSource;
|
use codex_protocol::protocol::SubAgentSource;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
use core_test_support::apps_test_server::AppsTestServer;
|
use core_test_support::apps_test_server::AppsTestServer;
|
||||||
@@ -40,21 +42,16 @@ use super::rmcp_client::remote_aware_stdio_server_bin;
|
|||||||
const SERVER_NAME: &str = "cached_rmcp";
|
const SERVER_NAME: &str = "cached_rmcp";
|
||||||
const NAMESPACE: &str = "mcp__cached_rmcp";
|
const NAMESPACE: &str = "mcp__cached_rmcp";
|
||||||
|
|
||||||
fn user_turn(prompt: &str) -> Op {
|
fn user_turn(prompt: &str) -> TurnInputRequest {
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: prompt.to_string(),
|
||||||
text: prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
responsesapi_client_metadata: None,
|
permission_profile: Some(PermissionProfile::Disabled),
|
||||||
additional_context: Default::default(),
|
..Default::default()
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
})
|
||||||
approval_policy: Some(AskForApproval::Never),
|
|
||||||
permission_profile: Some(PermissionProfile::Disabled),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_label(pid: &str) -> String {
|
fn process_label(pid: &str) -> String {
|
||||||
@@ -193,7 +190,7 @@ async fn mcp_calls_stay_bound_to_each_thread() -> anyhow::Result<()> {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
thread
|
thread
|
||||||
.submit(user_turn(&format!("Call the {SERVER_NAME} echo tool.")))
|
.start_or_steer_turn(user_turn(&format!("Call the {SERVER_NAME} echo tool.")))
|
||||||
.await?;
|
.await?;
|
||||||
let EventMsg::McpToolCallEnd(end) = wait_for_event(
|
let EventMsg::McpToolCallEnd(end) = wait_for_event(
|
||||||
thread,
|
thread,
|
||||||
@@ -362,7 +359,7 @@ async fn cached_http_mcp_starts_lazily_for_subagents(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
subagent
|
subagent
|
||||||
.submit(user_turn("Call the cached HTTP tool."))
|
.start_or_steer_turn(user_turn("Call the cached HTTP tool."))
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&subagent, |event| {
|
wait_for_event(&subagent, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -445,7 +442,10 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
fixture.codex.submit(user_turn("use the echo tool")).await?;
|
fixture
|
||||||
|
.codex
|
||||||
|
.start_or_steer_turn(user_turn("use the echo tool"))
|
||||||
|
.await?;
|
||||||
let first_pid = wait_for_new_pid(fs.as_ref(), &pid_file, /*previous_pid*/ None).await?;
|
let first_pid = wait_for_new_pid(fs.as_ref(), &pid_file, /*previous_pid*/ None).await?;
|
||||||
fs.write_file(&barrier_file, b"ready".to_vec(), /*sandbox*/ None)
|
fs.write_file(&barrier_file, b"ready".to_vec(), /*sandbox*/ None)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -515,7 +515,7 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
second_thread
|
second_thread
|
||||||
.submit(user_turn("Do not call any MCP tools."))
|
.start_or_steer_turn(user_turn("Do not call any MCP tools."))
|
||||||
.await?;
|
.await?;
|
||||||
let mut reported_ready_before_startup = false;
|
let mut reported_ready_before_startup = false;
|
||||||
wait_for_event(&second_thread, |event| {
|
wait_for_event(&second_thread, |event| {
|
||||||
@@ -578,7 +578,7 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
|||||||
let second_for_turn = Arc::clone(&second_thread);
|
let second_for_turn = Arc::clone(&second_thread);
|
||||||
let cached_turn = tokio::spawn(async move {
|
let cached_turn = tokio::spawn(async move {
|
||||||
second_for_turn
|
second_for_turn
|
||||||
.submit(user_turn("call the echo and cwd tools"))
|
.start_or_steer_turn(user_turn("call the echo and cwd tools"))
|
||||||
.await?;
|
.await?;
|
||||||
let mut unrelated_finished_tx = Some(unrelated_finished_tx);
|
let mut unrelated_finished_tx = Some(unrelated_finished_tx);
|
||||||
let mut saw_starting = false;
|
let mut saw_starting = false;
|
||||||
@@ -734,7 +734,7 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
interrupted_thread
|
interrupted_thread
|
||||||
.submit(user_turn("Start the cached MCP tool."))
|
.start_or_steer_turn(user_turn("Start the cached MCP tool."))
|
||||||
.await?;
|
.await?;
|
||||||
let interrupted_pid = wait_for_new_pid(fs.as_ref(), &pid_file, Some(&filtered_pid)).await?;
|
let interrupted_pid = wait_for_new_pid(fs.as_ref(), &pid_file, Some(&filtered_pid)).await?;
|
||||||
wait_for_event(&interrupted_thread, |event| {
|
wait_for_event(&interrupted_thread, |event| {
|
||||||
@@ -790,7 +790,7 @@ async fn cached_mcp_startup_is_eager_for_root_and_lazy_for_subagents() -> anyhow
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
interrupted_thread
|
interrupted_thread
|
||||||
.submit(user_turn("Retry the cached MCP tool."))
|
.start_or_steer_turn(user_turn("Retry the cached MCP tool."))
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&interrupted_thread, |event| {
|
wait_for_event(&interrupted_thread, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::Constrained;
|
use codex_config::Constrained;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_extension_api::ExtensionFuture;
|
use codex_extension_api::ExtensionFuture;
|
||||||
use codex_extension_api::ExtensionRegistryBuilder;
|
use codex_extension_api::ExtensionRegistryBuilder;
|
||||||
@@ -904,16 +905,10 @@ async fn apps_guidance_and_deferred_namespace_appear_after_recovery_within_a_tur
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "use an app after it recovers".into(),
|
||||||
text: "use an app after it recovers".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let EventMsg::RequestUserInput(request) = wait_for_event(&test.codex, |event| {
|
let EventMsg::RequestUserInput(request) = wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::RequestUserInput(_))
|
matches!(event, EventMsg::RequestUserInput(_))
|
||||||
@@ -1093,16 +1088,10 @@ async fn later_follow_up_uses_background_recovered_apps_after_mid_thread_startup
|
|||||||
startup_control.fail_next_initialize_attempts(/*attempts*/ 1);
|
startup_control.fail_next_initialize_attempts(/*attempts*/ 1);
|
||||||
test.codex.submit(Op::RefreshMcpServers).await?;
|
test.codex.submit(Op::RefreshMcpServers).await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "use Calendar after transient Apps startup failures".into(),
|
||||||
text: "use Calendar after transient Apps startup failures".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
tokio::time::timeout(Duration::from_secs(5), async {
|
tokio::time::timeout(Duration::from_secs(5), async {
|
||||||
let mut turn_complete = false;
|
let mut turn_complete = false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::test_support::CloudConfigBundleFixture;
|
use codex_config::test_support::CloudConfigBundleFixture;
|
||||||
use codex_config::types::AppToolApproval;
|
use codex_config::types::AppToolApproval;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
@@ -20,6 +21,7 @@ use codex_protocol::protocol::AskForApproval;
|
|||||||
use codex_protocol::protocol::ElicitationAction;
|
use codex_protocol::protocol::ElicitationAction;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -111,23 +113,20 @@ async fn submit_user_turn(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.cwd.path());
|
turn_permission_fields(permission_profile, test.cwd.path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: collaboration_mode.or({
|
collaboration_mode: collaboration_mode.or({
|
||||||
Some(codex_protocol::config_types::CollaborationMode {
|
Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
@@ -135,8 +134,8 @@ async fn submit_user_turn(
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,13 +148,13 @@ mod tool_harness;
|
|||||||
mod tool_parallelism;
|
mod tool_parallelism;
|
||||||
mod tools;
|
mod tools;
|
||||||
mod truncation;
|
mod truncation;
|
||||||
|
mod turn_input_submission;
|
||||||
mod turn_state;
|
mod turn_state;
|
||||||
mod unified_exec;
|
mod unified_exec;
|
||||||
mod unified_exec_process_events;
|
mod unified_exec_process_events;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod unified_exec_zsh_fork_approvals;
|
mod unified_exec_zsh_fork_approvals;
|
||||||
mod unstable_features_warning;
|
mod unstable_features_warning;
|
||||||
mod user_message_admission;
|
|
||||||
mod user_notification;
|
mod user_notification;
|
||||||
mod user_shell_cmd;
|
mod user_shell_cmd;
|
||||||
mod view_image;
|
mod view_image;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
@@ -13,7 +14,6 @@ use codex_protocol::openai_models::ModelsResponse;
|
|||||||
use codex_protocol::openai_models::ToolMode;
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
@@ -133,16 +133,10 @@ async fn response_for_remote_model(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "list tools".into(),
|
||||||
text: "list tools".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
@@ -329,16 +323,10 @@ async fn unsupported_code_mode_warning_is_emitted_each_turn() -> Result<()> {
|
|||||||
let mut warning_counts = Vec::new();
|
let mut warning_counts = Vec::new();
|
||||||
for prompt in ["first turn", "second turn"] {
|
for prompt in ["first turn", "second turn"] {
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: prompt.to_string(),
|
||||||
text: prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut warning_count = 0;
|
let mut warning_count = 0;
|
||||||
@@ -471,16 +459,10 @@ async fn remote_multi_agent_selector_uses_model_selected_before_first_turn() ->
|
|||||||
assert_eq!(test.codex.multi_agent_version(), None);
|
assert_eq!(test.codex.multi_agent_version(), None);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: ROOT_PROMPT.into(),
|
||||||
text: ROOT_PROMPT.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ use anyhow::Result;
|
|||||||
use codex_config::types::Personality;
|
use codex_config::types::Personality;
|
||||||
use codex_core::CodexThread;
|
use codex_core::CodexThread;
|
||||||
use codex_core::ForkSnapshot;
|
use codex_core::ForkSnapshot;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
use codex_history::RolloutLine;
|
use codex_history::RolloutLine;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_models_manager::manager::RefreshStrategy;
|
use codex_models_manager::manager::RefreshStrategy;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::BaseInstructionsProvenance;
|
use codex_protocol::models::BaseInstructionsProvenance;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
@@ -47,30 +51,24 @@ use pretty_assertions::assert_eq;
|
|||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
use wiremock::MockServer;
|
use wiremock::MockServer;
|
||||||
|
|
||||||
fn read_only_user_turn(test: &TestCodex, items: Vec<UserInput>, model: String) -> Op {
|
fn read_only_user_turn(test: &TestCodex, items: Vec<UserInput>, model: String) -> TurnInputRequest {
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::read_only(), test.cwd_path());
|
turn_permission_fields(PermissionProfile::read_only(), test.cwd_path());
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(items).with_thread_settings(ThreadSettingsOverrides {
|
||||||
items,
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
final_output_json_schema: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
responsesapi_client_metadata: None,
|
sandbox_policy: Some(sandbox_policy),
|
||||||
additional_context: Default::default(),
|
permission_profile,
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
mode: ModeKind::Default,
|
||||||
approval_policy: Some(AskForApproval::Never),
|
settings: Settings {
|
||||||
sandbox_policy: Some(sandbox_policy),
|
model,
|
||||||
permission_profile,
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
developer_instructions: None,
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
},
|
||||||
settings: codex_protocol::config_types::Settings {
|
}),
|
||||||
model,
|
..Default::default()
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
})
|
||||||
developer_instructions: None,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn submit_model_turn(
|
async fn submit_model_turn(
|
||||||
@@ -80,16 +78,13 @@ async fn submit_model_turn(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
thread_settings.model = Some(model.to_string());
|
thread_settings.model = Some(model.to_string());
|
||||||
thread
|
thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "switch models".into(),
|
text: "switch models".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(thread_settings),
|
||||||
responsesapi_client_metadata: None,
|
)
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings,
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -371,7 +366,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
|||||||
let next_model = "gpt-5.4";
|
let next_model = "gpt-5.4";
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
@@ -384,7 +379,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(next_model.to_string()),
|
model: Some(next_model.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -392,7 +387,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "switch models".into(),
|
text: "switch models".into(),
|
||||||
@@ -466,7 +461,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
|||||||
let next_model = "exp-codex-personality";
|
let next_model = "exp-codex-personality";
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "hello".into(),
|
text: "hello".into(),
|
||||||
@@ -479,7 +474,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(next_model.to_string()),
|
model: Some(next_model.to_string()),
|
||||||
personality: Some(Personality::Pragmatic),
|
personality: Some(Personality::Pragmatic),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -488,7 +483,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "switch model and personality".into(),
|
text: "switch model and personality".into(),
|
||||||
@@ -793,7 +788,7 @@ async fn model_change_from_multimodal_to_text_strips_prior_media_content() -> Re
|
|||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![
|
vec![
|
||||||
UserInput::Image {
|
UserInput::Image {
|
||||||
@@ -814,7 +809,7 @@ async fn model_change_from_multimodal_to_text_strips_prior_media_content() -> Re
|
|||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "second turn".to_string(),
|
text: "second turn".to_string(),
|
||||||
@@ -911,7 +906,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "generate a lobster".to_string(),
|
text: "generate a lobster".to_string(),
|
||||||
@@ -923,7 +918,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
|||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "describe the generated image".to_string(),
|
text: "describe the generated image".to_string(),
|
||||||
@@ -1008,7 +1003,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "generate a lobster".to_string(),
|
text: "generate a lobster".to_string(),
|
||||||
@@ -1020,7 +1015,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
|||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "describe the generated image".to_string(),
|
text: "describe the generated image".to_string(),
|
||||||
@@ -1107,7 +1102,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "generate a lobster".to_string(),
|
text: "generate a lobster".to_string(),
|
||||||
@@ -1127,7 +1122,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "after rollback".to_string(),
|
text: "after rollback".to_string(),
|
||||||
@@ -1283,7 +1278,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
|||||||
);
|
);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "use larger model".into(),
|
text: "use larger model".into(),
|
||||||
@@ -1318,7 +1313,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(smaller_model_slug.to_string()),
|
model: Some(smaller_model_slug.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1326,7 +1321,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&test,
|
&test,
|
||||||
vec![UserInput::Text {
|
vec![UserInput::Text {
|
||||||
text: "switch to smaller model".into(),
|
text: "switch to smaller model".into(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::sync::Mutex;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::types::Personality;
|
use codex_config::types::Personality;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||||
use codex_extension_api::ContextualUserFragment;
|
use codex_extension_api::ContextualUserFragment;
|
||||||
@@ -17,10 +18,13 @@ use codex_extension_api::TurnInputContext;
|
|||||||
use codex_extension_api::TurnInputContributor;
|
use codex_extension_api::TurnInputContributor;
|
||||||
use codex_extension_api::TurnInputEnvironment;
|
use codex_extension_api::TurnInputEnvironment;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -201,15 +205,12 @@ async fn model_visible_environment_context_preserves_foreign_workspace_roots() -
|
|||||||
let foreign_root = PathUri::parse("file:///C:/workspace")?;
|
let foreign_root = PathUri::parse("file:///C:/workspace")?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "inspect the workspace".into(),
|
text: "inspect the workspace".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
@@ -219,8 +220,8 @@ async fn model_visible_environment_context_preserves_foreign_workspace_roots() -
|
|||||||
}],
|
}],
|
||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -282,30 +283,27 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
|
|||||||
turn_permission_fields(PermissionProfile::read_only(), first_turn_cwd.as_path());
|
turn_permission_fields(PermissionProfile::read_only(), first_turn_cwd.as_path());
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "first turn".into(),
|
text: "first turn".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(first_turn_cwd)),
|
environments: Some(local_selections(first_turn_cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(first_sandbox_policy),
|
sandbox_policy: Some(first_sandbox_policy),
|
||||||
permission_profile: first_permission_profile,
|
permission_profile: first_permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -317,31 +315,28 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
|
|||||||
preturn_context_diff_cwd.as_path(),
|
preturn_context_diff_cwd.as_path(),
|
||||||
);
|
);
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "second turn with context updates".into(),
|
text: "second turn with context updates".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(preturn_context_diff_cwd)),
|
environments: Some(local_selections(preturn_context_diff_cwd)),
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
sandbox_policy: Some(second_sandbox_policy),
|
sandbox_policy: Some(second_sandbox_policy),
|
||||||
permission_profile: second_permission_profile,
|
permission_profile: second_permission_profile,
|
||||||
personality: Some(Personality::Friendly),
|
personality: Some(Personality::Friendly),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -408,30 +403,27 @@ async fn snapshot_model_visible_layout_cwd_change_refreshes_agents() -> Result<(
|
|||||||
turn_permission_fields(PermissionProfile::read_only(), cwd_one.as_path());
|
turn_permission_fields(PermissionProfile::read_only(), cwd_one.as_path());
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "first turn in agents_one".into(),
|
text: "first turn in agents_one".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_one.clone())),
|
environments: Some(local_selections(cwd_one.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(first_sandbox_policy),
|
sandbox_policy: Some(first_sandbox_policy),
|
||||||
permission_profile: first_permission_profile,
|
permission_profile: first_permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -441,30 +433,27 @@ async fn snapshot_model_visible_layout_cwd_change_refreshes_agents() -> Result<(
|
|||||||
let (second_sandbox_policy, second_permission_profile) =
|
let (second_sandbox_policy, second_permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::read_only(), cwd_two.as_path());
|
turn_permission_fields(PermissionProfile::read_only(), cwd_two.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "second turn in agents_two".into(),
|
text: "second turn in agents_two".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_two)),
|
environments: Some(local_selections(cwd_two)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(second_sandbox_policy),
|
sandbox_policy: Some(second_sandbox_policy),
|
||||||
permission_profile: second_permission_profile,
|
permission_profile: second_permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -520,16 +509,10 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "seed resume history".into(),
|
||||||
text: "seed resume history".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
let initial_request = initial_mock.single_request();
|
let initial_request = initial_mock.single_request();
|
||||||
@@ -564,31 +547,28 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
|
|||||||
);
|
);
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "resume and change personality".into(),
|
text: "resume and change personality".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(resume_override_cwd)),
|
environments: Some(local_selections(resume_override_cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
personality: Some(Personality::Friendly),
|
personality: Some(Personality::Friendly),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: resumed.session_configured.model.clone(),
|
model: resumed.session_configured.model.clone(),
|
||||||
reasoning_effort: resumed.config.model_reasoning_effort.clone(),
|
reasoning_effort: resumed.config.model_reasoning_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -633,16 +613,10 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "seed resume history".into(),
|
||||||
text: "seed resume history".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
let initial_request = initial_mock.single_request();
|
let initial_request = initial_mock.single_request();
|
||||||
@@ -668,7 +642,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
|
|||||||
let resume_override_cwd = resume_override_cwd.abs();
|
let resume_override_cwd = resume_override_cwd.abs();
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&resumed.codex,
|
&resumed.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
environments: Some(local_selections(resume_override_cwd)),
|
environments: Some(local_selections(resume_override_cwd)),
|
||||||
model: Some("gpt-5.2".to_string()),
|
model: Some("gpt-5.2".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -677,16 +651,10 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
|
|||||||
.await?;
|
.await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first resumed turn after model override".into(),
|
||||||
text: "first resumed turn after model override".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use core_test_support::test_codex::local_selections;
|
use core_test_support::test_codex::local_selections;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -9,7 +10,10 @@ use chrono::Utc;
|
|||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_models_manager::client_version_to_whole;
|
use codex_models_manager::client_version_to_whole;
|
||||||
use codex_models_manager::manager::RefreshStrategy;
|
use codex_models_manager::manager::RefreshStrategy;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
@@ -21,7 +25,7 @@ use codex_protocol::openai_models::ReasoningEffortPreset;
|
|||||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||||
use codex_protocol::openai_models::default_input_modalities;
|
use codex_protocol::openai_models::default_input_modalities;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
use core_test_support::responses::ev_assistant_message;
|
use core_test_support::responses::ev_assistant_message;
|
||||||
@@ -98,30 +102,27 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, test.cwd_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.cwd_path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hi".into(),
|
text: "hi".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(codex_protocol::protocol::AskForApproval::Never),
|
approval_policy: Some(codex_protocol::protocol::AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _ = wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
let _ = wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#![cfg(not(target_os = "windows"))]
|
#![cfg(not(target_os = "windows"))]
|
||||||
|
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use core_test_support::test_codex::local_selections;
|
use core_test_support::test_codex::local_selections;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -7,11 +8,14 @@ use std::time::Duration;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ModelsResponse;
|
use codex_protocol::openai_models::ModelsResponse;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::TempDirExt;
|
use core_test_support::TempDirExt;
|
||||||
use core_test_support::responses;
|
use core_test_support::responses;
|
||||||
@@ -106,30 +110,27 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "please run a tool".into(),
|
text: "please run a tool".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_path)),
|
environments: Some(local_selections(cwd_path)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _ = wait_for_event_with_timeout(
|
let _ = wait_for_event_with_timeout(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
@@ -6,7 +7,6 @@ use codex_protocol::openai_models::ReasoningEffort;
|
|||||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -74,19 +74,16 @@ async fn submit_turn(
|
|||||||
effort: Option<ReasoningEffort>,
|
effort: Option<ReasoningEffort>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.to_string(),
|
text: prompt.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
effort: effort.map(Some),
|
effort: effort.map(Some),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::AgentRoleConfig;
|
use codex_core::config::AgentRoleConfig;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ReasoningEffort;
|
use codex_protocol::openai_models::ReasoningEffort;
|
||||||
use codex_protocol::protocol::AgentStatus;
|
use codex_protocol::protocol::AgentStatus;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::Submission;
|
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::assert_parent_turn;
|
use core_test_support::responses::assert_parent_turn;
|
||||||
use core_test_support::responses::assert_root_turn;
|
use core_test_support::responses::assert_root_turn;
|
||||||
@@ -246,27 +246,18 @@ async fn cold_root_resume_restores_agent_identity_and_role_on_followup() -> Resu
|
|||||||
});
|
});
|
||||||
let initial = initial_builder.build_with_auto_env(&server).await?;
|
let initial = initial_builder.build_with_auto_env(&server).await?;
|
||||||
let root_thread_id = initial.session_configured.thread_id;
|
let root_thread_id = initial.session_configured.thread_id;
|
||||||
let mut op = vec![UserInput::Text {
|
|
||||||
text: INITIAL_PROMPT.to_string(),
|
|
||||||
text_elements: Vec::new(),
|
|
||||||
}]
|
|
||||||
.into();
|
|
||||||
if let Op::UserInput {
|
|
||||||
thread_settings, ..
|
|
||||||
} = &mut op
|
|
||||||
{
|
|
||||||
thread_settings.permission_profile = Some(PermissionProfile::Disabled);
|
|
||||||
}
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit_with_id(Submission {
|
.start_or_steer_turn(
|
||||||
id: "spoofed-root-turn".to_string(),
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
op,
|
text: INITIAL_PROMPT.to_string(),
|
||||||
client_user_message_id: None,
|
text_elements: Vec::new(),
|
||||||
trace: None,
|
}])
|
||||||
parent_turn_id: Some("spoofed-parent-turn".to_string()),
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
root_turn_id: Some("spoofed-foreign-root-turn".to_string()),
|
permission_profile: Some(PermissionProfile::Disabled),
|
||||||
})
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |event| {
|
wait_for_event(&initial.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -9,7 +10,6 @@ use codex_protocol::models::PermissionProfile;
|
|||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
@@ -206,15 +206,12 @@ async fn two_exec_servers_isolate_workspace_write_roots() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "write one file in each environment".into(),
|
text: "write one file in each environment".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
environments,
|
environments,
|
||||||
@@ -223,8 +220,8 @@ async fn two_exec_servers_isolate_workspace_write_roots() -> Result<()> {
|
|||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event_with_timeout(
|
wait_for_event_with_timeout(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::types::ApprovalsReviewer;
|
use codex_config::types::ApprovalsReviewer;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
|
||||||
@@ -11,6 +12,9 @@ use codex_protocol::approvals::NetworkApprovalContext;
|
|||||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||||
use codex_protocol::approvals::NetworkPolicyAmendment;
|
use codex_protocol::approvals::NetworkPolicyAmendment;
|
||||||
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
@@ -19,6 +23,7 @@ use codex_protocol::protocol::ExecApprovalRequestEvent;
|
|||||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||||
use codex_protocol::protocol::TurnEnvironmentSelections;
|
use codex_protocol::protocol::TurnEnvironmentSelections;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -1664,31 +1669,28 @@ async fn submit_managed_network_turn(
|
|||||||
TurnEnvironmentSelections::new(test.config.cwd.clone(), environments);
|
TurnEnvironmentSelections::new(test.config.cwd.clone(), environments);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(turn_environment_selections),
|
environments: Some(turn_environment_selections),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(approvals_reviewer),
|
approvals_reviewer: Some(approvals_reviewer),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_otel::SessionTelemetry;
|
use codex_otel::SessionTelemetry;
|
||||||
@@ -128,16 +129,10 @@ async fn responses_api_emits_api_request_event() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -193,16 +188,10 @@ async fn process_sse_emits_tracing_for_output_item() {
|
|||||||
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
|
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -239,16 +228,10 @@ async fn process_sse_emits_failed_event_on_parse_error() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -286,16 +269,10 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed()
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -353,16 +330,10 @@ async fn process_sse_failed_event_records_response_error_message() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -418,16 +389,10 @@ async fn process_sse_failed_event_logs_parse_error() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -470,16 +435,10 @@ async fn process_sse_failed_event_logs_missing_error() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -531,16 +490,10 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -592,16 +545,10 @@ async fn process_sse_emits_completed_telemetry() {
|
|||||||
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
|
let TestCodex { codex, .. } = test_codex().build(&server).await.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -678,16 +625,10 @@ async fn turn_and_completed_response_spans_record_token_usage() {
|
|||||||
let TestCodex { codex, .. } = test;
|
let TestCodex { codex, .. } = test;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -768,16 +709,10 @@ async fn handle_responses_span_records_response_kind_and_tool_name() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -862,16 +797,10 @@ async fn record_responses_sets_span_fields_for_response_events() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -951,16 +880,10 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1027,16 +950,10 @@ async fn handle_response_item_records_tool_result_for_function_call() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1104,16 +1021,10 @@ async fn handle_response_item_records_tool_result_for_shell_command_call() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1277,16 +1188,10 @@ async fn handle_shell_command_autoapprove_from_config_records_tool_decision() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1332,16 +1237,10 @@ async fn handle_shell_command_user_approved_records_tool_decision() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "approved".into(),
|
||||||
text: "approved".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1402,16 +1301,10 @@ async fn handle_shell_command_user_approved_for_session_records_tool_decision()
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "persist".into(),
|
||||||
text: "persist".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1472,16 +1365,10 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "retry".into(),
|
||||||
text: "retry".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1542,16 +1429,10 @@ async fn handle_shell_command_user_denies_records_tool_decision() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "deny".into(),
|
||||||
text: "deny".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1612,16 +1493,10 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "persist".into(),
|
||||||
text: "persist".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1683,16 +1558,10 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "deny".into(),
|
||||||
text: "deny".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ use core_test_support::test_codex::local_selections;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use codex_core::CodexThread;
|
use codex_core::CodexThread;
|
||||||
|
use codex_core::StartIfIdleSubmission;
|
||||||
use codex_core::TurnInput;
|
use codex_core::TurnInput;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
|
use codex_core::TurnInputSubmission;
|
||||||
use codex_core::config::CurrentTimeReminderConfig;
|
use codex_core::config::CurrentTimeReminderConfig;
|
||||||
use codex_extension_items::ExtensionItem;
|
use codex_extension_items::ExtensionItem;
|
||||||
use codex_extension_items::sleep::SleepItem;
|
use codex_extension_items::sleep::SleepItem;
|
||||||
@@ -70,14 +73,13 @@ async fn idle_response_items_include_pending_mailbox_in_first_request() -> anyho
|
|||||||
let test = test_codex().build_with_auto_env(&server).await?;
|
let test = test_codex().build_with_auto_env(&server).await?;
|
||||||
|
|
||||||
submit_queue_only_agent_mail(test.codex.as_ref(), "pending mailbox input").await;
|
submit_queue_only_agent_mail(test.codex.as_ref(), "pending mailbox input").await;
|
||||||
test.codex
|
let submission = test
|
||||||
.try_start_turn_if_idle(vec![TurnInput::ResponseItem(
|
.codex
|
||||||
responses::user_message_item("automatic response item").into(),
|
.start_turn_if_idle(TurnInputRequest::new(TurnInput::ResponseItem(
|
||||||
)])
|
responses::user_message_item("automatic response item"),
|
||||||
.await
|
)))
|
||||||
.map_err(|error| {
|
.await?;
|
||||||
anyhow::anyhow!("idle response items were rejected: {:?}", error.reason())
|
assert!(matches!(submission, StartIfIdleSubmission::Started { .. }));
|
||||||
})?;
|
|
||||||
wait_for_turn_complete(test.codex.as_ref()).await;
|
wait_for_turn_complete(test.codex.as_ref()).await;
|
||||||
|
|
||||||
let request = response.single_request();
|
let request = response.single_request();
|
||||||
@@ -144,13 +146,14 @@ async fn assert_idle_user_input_reaches_the_first_model_request(
|
|||||||
text: "queued user input reaches the first request".to_string(),
|
text: "queued user input reaches the first request".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}];
|
}];
|
||||||
test.codex
|
let submission = test
|
||||||
.try_start_turn_if_idle(vec![TurnInput::UserInput {
|
.codex
|
||||||
|
.start_turn_if_idle(TurnInputRequest::new(TurnInput::UserInput {
|
||||||
content: expected_input.clone(),
|
content: expected_input.clone(),
|
||||||
client_id: Some("queued-user-message".to_string()),
|
client_id: Some("queued-user-message".to_string()),
|
||||||
}])
|
}))
|
||||||
.await
|
.await?;
|
||||||
.map_err(|error| anyhow::anyhow!("idle user input was rejected: {:?}", error.reason()))?;
|
assert!(matches!(submission, StartIfIdleSubmission::Started { .. }));
|
||||||
|
|
||||||
let user_message = core_test_support::wait_for_event_match(test.codex.as_ref(), |event| {
|
let user_message = core_test_support::wait_for_event_match(test.codex.as_ref(), |event| {
|
||||||
let EventMsg::ItemCompleted(event) = event else {
|
let EventMsg::ItemCompleted(event) = event else {
|
||||||
@@ -276,16 +279,10 @@ async fn build_codex(server: &StreamingSseServer) -> Arc<CodexThread> {
|
|||||||
|
|
||||||
async fn submit_user_input(codex: &CodexThread, text: &str) {
|
async fn submit_user_input(codex: &CodexThread, text: &str) {
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("submit user input");
|
.expect("submit user input");
|
||||||
}
|
}
|
||||||
@@ -294,48 +291,40 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await
|
.await
|
||||||
.expect("submit user turn");
|
.expect("submit user turn");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn steer_user_input(codex: &CodexThread, text: &str) {
|
async fn steer_user_input(codex: &CodexThread, text: &str) {
|
||||||
codex
|
let submission = codex
|
||||||
.steer_input(
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
/*additional_context*/ Default::default(),
|
|
||||||
/*expected_turn_id*/ None,
|
|
||||||
/*client_user_message_id*/ None,
|
|
||||||
/*responsesapi_client_metadata*/ None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.expect("steer user input");
|
.expect("steer user input");
|
||||||
|
assert!(matches!(submission, TurnInputSubmission::Steered { .. }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn enqueue_queue_only_agent_mail(codex: &CodexThread, text: &str) {
|
async fn enqueue_queue_only_agent_mail(codex: &CodexThread, text: &str) {
|
||||||
@@ -759,16 +748,10 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
|
|||||||
.codex;
|
.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first prompt".into(),
|
||||||
text: "first prompt".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -778,16 +761,10 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "second prompt".into(),
|
||||||
text: "second prompt".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::ConfigLayerStack;
|
use codex_config::ConfigLayerStack;
|
||||||
use codex_core::ForkSnapshot;
|
use codex_core::ForkSnapshot;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::context::ApprovalPromptContext;
|
use codex_core::context::ApprovalPromptContext;
|
||||||
use codex_core::context::ContextualUserFragment;
|
use codex_core::context::ContextualUserFragment;
|
||||||
@@ -15,7 +16,7 @@ use codex_protocol::openai_models::PermissionMessages;
|
|||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use core_test_support::responses::ResponsesRequest;
|
use core_test_support::responses::ResponsesRequest;
|
||||||
@@ -84,16 +85,10 @@ async fn submit_text_turn(
|
|||||||
text: &str,
|
text: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -171,7 +166,7 @@ async fn model_change_appends_new_catalog_approval_message() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(second_slug.to_string()),
|
model: Some(second_slug.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -304,7 +299,7 @@ async fn catalog_permission_message_is_sent_initially_and_after_model_change() -
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(second_slug.to_string()),
|
model: Some(second_slug.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -393,16 +388,10 @@ async fn permissions_message_sent_once_on_start() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -433,22 +422,16 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -456,16 +439,10 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -502,30 +479,18 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -562,22 +527,16 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -585,16 +544,10 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -638,22 +591,16 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&initial.codex,
|
&initial.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -662,32 +609,20 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
let resumed = builder.restart(&server, &initial).await?;
|
let resumed = builder.restart(&server, &initial).await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "after resume".into(),
|
||||||
text: "after resume".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -737,22 +672,16 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&initial.codex,
|
&initial.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -761,16 +690,10 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||||||
|
|
||||||
initial
|
initial
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -783,16 +706,10 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||||||
let resumed = builder.restart(&server, &initial).await?;
|
let resumed = builder.restart(&server, &initial).await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "after resume".into(),
|
||||||
text: "after resume".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&resumed.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -818,16 +735,10 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
forked
|
forked
|
||||||
.thread
|
.thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "after fork".into(),
|
||||||
text: "after fork".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -879,16 +790,10 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello".into(),
|
||||||
text: "hello".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use codex_config::types::Personality;
|
use codex_config::types::Personality;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_models_manager::bundled_models_response;
|
use codex_models_manager::bundled_models_response;
|
||||||
use codex_models_manager::manager::RefreshStrategy;
|
use codex_models_manager::manager::RefreshStrategy;
|
||||||
use codex_models_manager::manager::SharedModelsManager;
|
use codex_models_manager::manager::SharedModelsManager;
|
||||||
use codex_models_manager::model_info::BASE_INSTRUCTIONS;
|
use codex_models_manager::model_info::BASE_INSTRUCTIONS;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
@@ -18,7 +22,7 @@ use codex_protocol::openai_models::TruncationPolicyConfig;
|
|||||||
use codex_protocol::openai_models::default_input_modalities;
|
use codex_protocol::openai_models::default_input_modalities;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::mount_models_once;
|
use core_test_support::responses::mount_models_once;
|
||||||
use core_test_support::responses::mount_sse_once;
|
use core_test_support::responses::mount_sse_once;
|
||||||
@@ -47,7 +51,7 @@ fn read_only_text_turn(
|
|||||||
text: &str,
|
text: &str,
|
||||||
model: String,
|
model: String,
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
) -> Op {
|
) -> TurnInputRequest {
|
||||||
let personality = None;
|
let personality = None;
|
||||||
read_only_text_turn_with_personality(test, text, model, approval_policy, personality)
|
read_only_text_turn_with_personality(test, text, model, approval_policy, personality)
|
||||||
}
|
}
|
||||||
@@ -58,34 +62,29 @@ fn read_only_text_turn_with_personality(
|
|||||||
model: String,
|
model: String,
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
personality: Option<Personality>,
|
personality: Option<Personality>,
|
||||||
) -> Op {
|
) -> TurnInputRequest {
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::read_only(), test.cwd_path());
|
turn_permission_fields(PermissionProfile::read_only(), test.cwd_path());
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.into(),
|
||||||
text: text.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
responsesapi_client_metadata: None,
|
approval_policy: Some(approval_policy),
|
||||||
additional_context: Default::default(),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
permission_profile,
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
personality,
|
||||||
approval_policy: Some(approval_policy),
|
collaboration_mode: Some(CollaborationMode {
|
||||||
sandbox_policy: Some(sandbox_policy),
|
mode: ModeKind::Default,
|
||||||
permission_profile,
|
settings: Settings {
|
||||||
personality,
|
model,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
developer_instructions: None,
|
||||||
settings: codex_protocol::config_types::Settings {
|
},
|
||||||
model,
|
}),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
..Default::default()
|
||||||
developer_instructions: None,
|
})
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -103,7 +102,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -141,7 +140,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -186,7 +185,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -245,7 +244,7 @@ async fn config_personality_none_strips_baked_personality_section() -> anyhow::R
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -282,7 +281,7 @@ async fn config_personality_none_preserves_explicit_base_instructions() -> anyho
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -315,7 +314,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -356,7 +355,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -368,7 +367,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
personality: Some(Personality::Friendly),
|
personality: Some(Personality::Friendly),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -376,7 +375,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -432,7 +431,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -444,7 +443,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
personality: Some(Personality::Pragmatic),
|
personality: Some(Personality::Pragmatic),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -452,7 +451,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -552,7 +551,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -564,7 +563,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
personality: Some(Personality::Pragmatic),
|
personality: Some(Personality::Pragmatic),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -572,7 +571,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
test.session_configured.model.clone(),
|
test.session_configured.model.clone(),
|
||||||
@@ -702,7 +701,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
|||||||
wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await;
|
wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn_with_personality(
|
.start_or_steer_turn(read_only_text_turn_with_personality(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
remote_slug.to_string(),
|
remote_slug.to_string(),
|
||||||
@@ -830,7 +829,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||||||
wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await;
|
wait_for_model_available(&test.thread_manager.get_models_manager(), remote_slug).await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
remote_slug.to_string(),
|
remote_slug.to_string(),
|
||||||
@@ -842,7 +841,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&test.codex,
|
&test.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
personality: Some(Personality::Friendly),
|
personality: Some(Personality::Friendly),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -850,7 +849,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(read_only_text_turn(
|
.start_or_steer_turn(read_only_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"hello",
|
"hello",
|
||||||
remote_slug.to_string(),
|
remote_slug.to_string(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::time::Duration;
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core_plugins::store::PluginStore;
|
use codex_core_plugins::store::PluginStore;
|
||||||
use codex_extension_api::ExtensionRegistry;
|
use codex_extension_api::ExtensionRegistry;
|
||||||
@@ -16,10 +17,13 @@ use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;
|
|||||||
use codex_model_provider_info::OPENAI_PROVIDER_ID;
|
use codex_model_provider_info::OPENAI_PROVIDER_ID;
|
||||||
use codex_plugin::PluginId;
|
use codex_plugin::PluginId;
|
||||||
use codex_protocol::auth::AuthMode;
|
use codex_protocol::auth::AuthMode;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_skills_extension::SkillsExtensionConfig;
|
use codex_skills_extension::SkillsExtensionConfig;
|
||||||
use codex_skills_extension::install;
|
use codex_skills_extension::install;
|
||||||
@@ -415,30 +419,27 @@ async fn persisted_remote_plugin_command_attribution_flows_through_turn_context(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::read_only(), cwd.as_path());
|
turn_permission_fields(PermissionProfile::read_only(), cwd.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![codex_protocol::user_input::UserInput::Text {
|
TurnInputRequest::user_input(vec![codex_protocol::user_input::UserInput::Text {
|
||||||
text: "run the remote plugin script".into(),
|
text: "run the remote plugin script".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin = wait_for_event_match(&codex, |event| match event {
|
let begin = wait_for_event_match(&codex, |event| match event {
|
||||||
@@ -503,16 +504,10 @@ async fn agent_plugin_skills_use_shared_catalog_and_direct_child_discovery() ->
|
|||||||
|
|
||||||
test_codex
|
test_codex
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Skill {
|
||||||
items: vec![UserInput::Skill {
|
name: "acme.tools:review".into(),
|
||||||
name: "acme.tools:review".into(),
|
path: skill_path,
|
||||||
path: skill_path,
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let warning = wait_for_event(&test_codex.codex, |ev| {
|
let warning = wait_for_event(&test_codex.codex, |ev| {
|
||||||
matches!(
|
matches!(
|
||||||
@@ -618,16 +613,10 @@ async fn plugin_skill_product_policy_and_migrated_command_precedence_reach_agent
|
|||||||
);
|
);
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Inspect the available plugin skills.".to_string(),
|
||||||
text: "Inspect the available plugin skills.".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -675,16 +664,10 @@ async fn legacy_plugin_skill_prompt_remains_complete() -> Result<()> {
|
|||||||
|
|
||||||
test_codex
|
test_codex
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Skill {
|
||||||
items: vec![UserInput::Skill {
|
name: "sample:sample-search".into(),
|
||||||
name: "sample:sample-search".into(),
|
path: skill_path,
|
||||||
path: skill_path,
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test_codex.codex, |ev| {
|
wait_for_event(&test_codex.codex, |ev| {
|
||||||
matches!(ev, EventMsg::TurnComplete(_))
|
matches!(ev, EventMsg::TurnComplete(_))
|
||||||
@@ -776,16 +759,10 @@ async fn agent_plugin_root_mcp_stdio_tool_round_trip_expands_reserved_paths() ->
|
|||||||
|
|
||||||
test_codex
|
test_codex
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "call the Agent Plugin echo tool".into(),
|
||||||
text: "call the Agent Plugin echo tool".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let end = wait_for_event(&test_codex.codex, |event| {
|
let end = wait_for_event(&test_codex.codex, |event| {
|
||||||
matches!(event, EventMsg::McpToolCallEnd(_))
|
matches!(event, EventMsg::McpToolCallEnd(_))
|
||||||
@@ -890,16 +867,12 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
|
|||||||
.await?
|
.await?
|
||||||
.thread;
|
.thread;
|
||||||
thread
|
thread
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![codex_protocol::user_input::UserInput::Text {
|
codex_protocol::user_input::UserInput::Text {
|
||||||
text: prompt.to_string(),
|
text: prompt.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
},
|
||||||
final_output_json_schema: None,
|
]))
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1106,16 +1079,12 @@ async fn explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins(
|
|||||||
wait_for_mcp_server(&codex, CODEX_APPS_MCP_SERVER_NAME).await?;
|
wait_for_mcp_server(&codex, CODEX_APPS_MCP_SERVER_NAME).await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
codex_protocol::user_input::UserInput::Mention {
|
||||||
name: "sample".into(),
|
name: "sample".into(),
|
||||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||||
}],
|
},
|
||||||
final_output_json_schema: None,
|
]))
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1190,16 +1159,12 @@ async fn explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth() ->
|
|||||||
wait_for_mcp_server(&codex, "sample").await?;
|
wait_for_mcp_server(&codex, "sample").await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
codex_protocol::user_input::UserInput::Mention {
|
||||||
name: "sample".into(),
|
name: "sample".into(),
|
||||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||||
}],
|
},
|
||||||
final_output_json_schema: None,
|
]))
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1291,13 +1256,7 @@ async fn explicitly_requested_mcp_waits_for_startup(request: ExplicitMcpRequest)
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![input]))
|
||||||
items: vec![input],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
tokio::time::sleep(Duration::from_millis(1200)).await;
|
tokio::time::sleep(Duration::from_millis(1200)).await;
|
||||||
assert!(
|
assert!(
|
||||||
@@ -1372,16 +1331,12 @@ async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> {
|
|||||||
let codex = Arc::clone(&test_codex.codex);
|
let codex = Arc::clone(&test_codex.codex);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
codex_protocol::user_input::UserInput::Mention {
|
||||||
name: "sample".into(),
|
name: "sample".into(),
|
||||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||||
}],
|
},
|
||||||
final_output_json_schema: None,
|
]))
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1427,16 +1382,10 @@ async fn explicit_plugin_skill_invocation_tracks_remote_plugin_id() -> Result<()
|
|||||||
let codex = Arc::clone(&test_codex.codex);
|
let codex = Arc::clone(&test_codex.codex);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Skill {
|
||||||
items: vec![UserInput::Skill {
|
name: "sample:sample-search".into(),
|
||||||
name: "sample:sample-search".into(),
|
path: skill_path,
|
||||||
path: skill_path,
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1512,16 +1461,10 @@ async fn implicit_plugin_skill_invocation_tracks_remote_plugin_id(
|
|||||||
let codex = Arc::clone(&test_codex.codex);
|
let codex = Arc::clone(&test_codex.codex);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "inspect the sample skill".into(),
|
||||||
text: "inspect the sample skill".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::shell::default_user_shell;
|
use codex_core::shell::default_user_shell;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_prompts::APPLY_PATCH_TOOL_INSTRUCTIONS;
|
use codex_prompts::APPLY_PATCH_TOOL_INSTRUCTIONS;
|
||||||
@@ -17,7 +18,7 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
|
use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::TempDirExt;
|
use core_test_support::TempDirExt;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -167,30 +168,18 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
|||||||
let base_instructions = model_info.get_model_instructions(config.personality);
|
let base_instructions = model_info.get_model_instructions(config.personality);
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -261,30 +250,18 @@ async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> an
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -340,30 +317,18 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -442,16 +407,10 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
|||||||
|
|
||||||
// First turn
|
// First turn
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -467,7 +426,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
|||||||
.expect("workspace profile should have legacy projection");
|
.expect("workspace profile should have legacy projection");
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile: Some(permission_profile),
|
permission_profile: Some(permission_profile),
|
||||||
@@ -480,16 +439,10 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
|||||||
|
|
||||||
// Second turn after overrides
|
// Second turn after overrides
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 2".into(),
|
||||||
text: "hello 2".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -569,7 +522,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
model: Some("gpt-5.4".to_string()),
|
model: Some("gpt-5.4".to_string()),
|
||||||
effort: Some(Some(ReasoningEffort::Low)),
|
effort: Some(Some(ReasoningEffort::Low)),
|
||||||
@@ -580,16 +533,10 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first message".into(),
|
||||||
text: "first message".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -728,16 +675,10 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
|||||||
|
|
||||||
// First turn
|
// First turn
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "hello 1".into(),
|
||||||
text: "hello 1".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -753,15 +694,12 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, new_cwd.path());
|
turn_permission_fields(permission_profile, new_cwd.path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello 2".into(),
|
text: "hello 2".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(new_cwd.abs())),
|
environments: Some(local_selections(new_cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
@@ -770,8 +708,8 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
|
|||||||
effort: Some(Some(ReasoningEffort::High)),
|
effort: Some(Some(ReasoningEffort::High)),
|
||||||
summary: Some(ReasoningSummary::Detailed),
|
summary: Some(ReasoningSummary::Detailed),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -875,58 +813,52 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
|||||||
let default_summary = config.model_reasoning_summary;
|
let default_summary = config.model_reasoning_summary;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello 1".into(),
|
text: "hello 1".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(default_cwd.clone())),
|
environments: Some(local_selections(default_cwd.clone())),
|
||||||
approval_policy: Some(default_approval_policy),
|
approval_policy: Some(default_approval_policy),
|
||||||
sandbox_policy: Some(default_sandbox_policy.clone()),
|
sandbox_policy: Some(default_sandbox_policy.clone()),
|
||||||
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: default_model.clone(),
|
model: default_model.clone(),
|
||||||
reasoning_effort: default_effort.clone(),
|
reasoning_effort: default_effort.clone(),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello 2".into(),
|
text: "hello 2".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(default_cwd.clone())),
|
environments: Some(local_selections(default_cwd.clone())),
|
||||||
approval_policy: Some(default_approval_policy),
|
approval_policy: Some(default_approval_policy),
|
||||||
sandbox_policy: Some(default_sandbox_policy.clone()),
|
sandbox_policy: Some(default_sandbox_policy.clone()),
|
||||||
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: default_model.clone(),
|
model: default_model.clone(),
|
||||||
reasoning_effort: default_effort,
|
reasoning_effort: default_effort,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
@@ -1014,61 +946,55 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
|||||||
let default_summary = config.model_reasoning_summary;
|
let default_summary = config.model_reasoning_summary;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello 1".into(),
|
text: "hello 1".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(default_cwd.clone())),
|
environments: Some(local_selections(default_cwd.clone())),
|
||||||
approval_policy: Some(default_approval_policy),
|
approval_policy: Some(default_approval_policy),
|
||||||
sandbox_policy: Some(default_sandbox_policy.clone()),
|
sandbox_policy: Some(default_sandbox_policy.clone()),
|
||||||
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: default_model,
|
model: default_model,
|
||||||
reasoning_effort: default_effort,
|
reasoning_effort: default_effort,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, default_cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, default_cwd.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello 2".into(),
|
text: "hello 2".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(default_cwd.clone())),
|
environments: Some(local_selections(default_cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
summary: Some(ReasoningSummary::Detailed),
|
summary: Some(ReasoningSummary::Detailed),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: "o3".to_string(),
|
model: "o3".to_string(),
|
||||||
reasoning_effort: Some(ReasoningEffort::High),
|
reasoning_effort: Some(ReasoningEffort::High),
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_response_created;
|
use core_test_support::responses::ev_response_created;
|
||||||
use core_test_support::responses::mount_sse_once;
|
use core_test_support::responses::mount_sse_once;
|
||||||
@@ -40,16 +40,10 @@ async fn quota_exceeded_emits_single_error_event() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "quota?".into(),
|
||||||
text: "quota?".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use codex_config::config_toml::RealtimeWsVersion;
|
use codex_config::config_toml::RealtimeWsVersion;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::test_support::auth_manager_from_auth;
|
use codex_core::test_support::auth_manager_from_auth;
|
||||||
use codex_history::InitialHistory;
|
use codex_history::InitialHistory;
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
@@ -3054,16 +3055,10 @@ async fn conversation_user_text_turn_is_not_sent_to_realtime() -> Result<()> {
|
|||||||
|
|
||||||
let user_text = "typed follow-up for realtime";
|
let user_text = "typed follow-up for realtime";
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: user_text.to_string(),
|
||||||
text: user_text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_complete = wait_for_event_match(&test.codex, |event| match event {
|
let turn_complete = wait_for_event_match(&test.codex, |event| match event {
|
||||||
@@ -4695,16 +4690,10 @@ async fn inbound_handoff_request_steers_active_turn() -> Result<()> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first prompt".to_string(),
|
||||||
text: "first prompt".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use codex_api::AuthProvider;
|
|||||||
use codex_config::types::ApprovalsReviewer;
|
use codex_config::types::ApprovalsReviewer;
|
||||||
use codex_core::EnvironmentConfig;
|
use codex_core::EnvironmentConfig;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::WaitForEnvironmentToolConfig;
|
use codex_core::WaitForEnvironmentToolConfig;
|
||||||
use codex_core::compact::SUMMARIZATION_PROMPT;
|
use codex_core::compact::SUMMARIZATION_PROMPT;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
@@ -39,6 +40,9 @@ use codex_http_client::HttpClientFactory;
|
|||||||
use codex_http_client::OutboundProxyPolicy;
|
use codex_http_client::OutboundProxyPolicy;
|
||||||
use codex_protocol::capabilities::CapabilityRootLocation;
|
use codex_protocol::capabilities::CapabilityRootLocation;
|
||||||
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::FileSystemPermissions;
|
use codex_protocol::models::FileSystemPermissions;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::SandboxPermissions;
|
use codex_protocol::models::SandboxPermissions;
|
||||||
@@ -206,30 +210,27 @@ async fn submit_turn_with_approval_and_environments(
|
|||||||
environments,
|
environments,
|
||||||
);
|
);
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(turn_environment_selections),
|
environments: Some(turn_environment_selections),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(SandboxPolicy::new_read_only_policy()),
|
sandbox_policy: Some(SandboxPolicy::new_read_only_policy()),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -540,16 +541,10 @@ async fn settings_update_does_not_retarget_active_turn_environment() -> Result<(
|
|||||||
let next_cwd = next_workspace.path().abs();
|
let next_cwd = next_workspace.path().abs();
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "pause before continuing".into(),
|
||||||
text: "pause before continuing".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let request = wait_for_event_match(&test.codex, |event| match event {
|
let request = wait_for_event_match(&test.codex, |event| match event {
|
||||||
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
||||||
@@ -676,22 +671,19 @@ async fn deferred_executor_promotes_primary_environment_when_startup_completes()
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "wait for the primary environment".into(),
|
text: "wait for the primary environment".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![remote_selection, local_selection],
|
vec![remote_selection, local_selection],
|
||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let request = wait_for_event_match(&test.codex, |event| match event {
|
let request = wait_for_event_match(&test.codex, |event| match event {
|
||||||
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
||||||
@@ -1062,13 +1054,10 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread
|
thread
|
||||||
.submit(
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
vec![UserInput::Text {
|
text: prompt.to_string(),
|
||||||
text: prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}]
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
}
|
}
|
||||||
@@ -1359,15 +1348,12 @@ async fn deferred_executor_stays_pending_after_materialization() -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "wait for the environment".into(),
|
text: "wait for the environment".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![TurnEnvironmentSelection {
|
vec![TurnEnvironmentSelection {
|
||||||
@@ -1377,8 +1363,8 @@ async fn deferred_executor_stays_pending_after_materialization() -> Result<()> {
|
|||||||
}],
|
}],
|
||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
||||||
assert_eq!(provider.calls.load(Ordering::Relaxed), 0);
|
assert_eq!(provider.calls.load(Ordering::Relaxed), 0);
|
||||||
@@ -1503,22 +1489,19 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments(
|
|||||||
let mut created_threads = test.thread_manager.subscribe_thread_created();
|
let mut created_threads = test.thread_manager.subscribe_thread_created();
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "spawn after the environment becomes ready".into(),
|
text: "spawn after the environment becomes ready".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
expected_environments.clone(),
|
expected_environments.clone(),
|
||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
||||||
attach_tx.send(()).expect("attach remote environment");
|
attach_tx.send(()).expect("attach remote environment");
|
||||||
@@ -1655,15 +1638,12 @@ async fn deferred_executor_guardian_uses_newly_ready_step_environment() -> Resul
|
|||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "review a command after the remote environment becomes ready".into(),
|
text: "review a command after the remote environment becomes ready".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![remote_selection, local(local_cwd.clone())],
|
vec![remote_selection, local(local_cwd.clone())],
|
||||||
@@ -1671,8 +1651,8 @@ async fn deferred_executor_guardian_uses_newly_ready_step_environment() -> Resul
|
|||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_response_request_count(&responses, /*expected_count*/ 1).await;
|
wait_for_response_request_count(&responses, /*expected_count*/ 1).await;
|
||||||
attach_tx.send(()).expect("attach remote environment");
|
attach_tx.send(()).expect("attach remote environment");
|
||||||
@@ -1776,16 +1756,10 @@ async fn deferred_executor_loads_agents_md_when_environment_becomes_ready() -> R
|
|||||||
.context("thread startup should not wait for the remote environment")??;
|
.context("thread startup should not wait for the remote environment")??;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "load the environment instructions".into(),
|
||||||
text: "load the environment instructions".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
wait_for_response_request_count(&response_mock, /*expected_count*/ 1).await;
|
||||||
let agents_path = PathUri::from_abs_path(&test.config.cwd).join("AGENTS.md")?;
|
let agents_path = PathUri::from_abs_path(&test.config.cwd).join("AGENTS.md")?;
|
||||||
@@ -1890,16 +1864,10 @@ async fn deferred_executor_compaction_preserves_then_updates_environment_once()
|
|||||||
.context("thread startup should not wait for the remote environment")??;
|
.context("thread startup should not wait for the remote environment")??;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "wait for the environment".into(),
|
||||||
text: "wait for the environment".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let request = wait_for_event_match(&test.codex, |event| match event {
|
let request = wait_for_event_match(&test.codex, |event| match event {
|
||||||
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
EventMsg::RequestUserInput(request) => Some(request.clone()),
|
||||||
@@ -2269,15 +2237,12 @@ async fn remote_exec_materializes_target_roots_before_sandbox_selection() -> Res
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "try to read the denied remote workspace root".into(),
|
text: "try to read the denied remote workspace root".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
environments: Some(TurnEnvironmentSelections::new(
|
environments: Some(TurnEnvironmentSelections::new(
|
||||||
test.config.cwd.clone(),
|
test.config.cwd.clone(),
|
||||||
vec![
|
vec![
|
||||||
@@ -2296,17 +2261,17 @@ async fn remote_exec_materializes_target_roots_before_sandbox_selection() -> Res
|
|||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: test.session_configured.model.clone(),
|
model: test.session_configured.model.clone(),
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#![cfg(not(target_os = "windows"))]
|
#![cfg(not(target_os = "windows"))]
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_model_provider_info::ModelProviderInfo;
|
use codex_model_provider_info::ModelProviderInfo;
|
||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
@@ -22,7 +23,7 @@ use codex_protocol::openai_models::default_input_modalities;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ExecCommandSource;
|
use codex_protocol::protocol::ExecCommandSource;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::TempDirExt;
|
use core_test_support::TempDirExt;
|
||||||
use core_test_support::load_default_config_for_test;
|
use core_test_support::load_default_config_for_test;
|
||||||
@@ -206,16 +207,10 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "check context window".into(),
|
||||||
text: "check context window".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_started_event = wait_for_event(&codex, |event| {
|
let turn_started_event = wait_for_event(&codex, |event| {
|
||||||
@@ -273,16 +268,10 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "check context window".into(),
|
||||||
text: "check context window".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_started_event = wait_for_event(&codex, |event| {
|
let turn_started_event = wait_for_event(&codex, |event| {
|
||||||
@@ -339,16 +328,10 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "check context window".into(),
|
||||||
text: "check context window".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_started_event = wait_for_event(&codex, |event| {
|
let turn_started_event = wait_for_event(&codex, |event| {
|
||||||
@@ -418,16 +401,10 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "check model slug".into(),
|
||||||
text: "check model slug".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -468,16 +445,10 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning()
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "check namespaced model metadata".into(),
|
||||||
text: "check namespaced model metadata".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut fallback_warning_count = 0;
|
let mut fallback_warning_count = 0;
|
||||||
@@ -601,7 +572,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(REMOTE_MODEL_SLUG.to_string()),
|
model: Some(REMOTE_MODEL_SLUG.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -631,23 +602,20 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run call".into(),
|
text: "run call".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_path)),
|
environments: Some(local_selections(cwd_path)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
summary: Some(ReasoningSummary::Auto),
|
summary: Some(ReasoningSummary::Auto),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin_event = wait_for_event_match(&codex, |msg| match msg {
|
let begin_event = wait_for_event_match(&codex, |msg| match msg {
|
||||||
@@ -876,30 +844,27 @@ async fn remote_models_apply_legacy_instructions() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello base".into(),
|
text: "hello base".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_path.clone())),
|
environments: Some(local_selections(cwd_path.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
summary: Some(ReasoningSummary::Auto),
|
summary: Some(ReasoningSummary::Auto),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some(model.to_string()),
|
model: Some(model.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -909,23 +874,20 @@ async fn remote_models_apply_legacy_instructions() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "hello remote".into(),
|
text: "hello remote".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd_path)),
|
environments: Some(local_selections(cwd_path)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
summary: Some(ReasoningSummary::Auto),
|
summary: Some(ReasoningSummary::Auto),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#![cfg(not(target_os = "windows"))]
|
#![cfg(not(target_os = "windows"))]
|
||||||
|
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_login::CodexAuth;
|
use codex_login::CodexAuth;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
use core_test_support::responses::ev_response_created;
|
use core_test_support::responses::ev_response_created;
|
||||||
@@ -39,16 +39,10 @@ async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> any
|
|||||||
let codex = builder.build(&server).await?.codex;
|
let codex = builder.build(&server).await?.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "compress me".into(),
|
||||||
text: "compress me".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Wait until the task completes so the request definitely hit the server.
|
// Wait until the task completes so the request definitely hit the server.
|
||||||
@@ -89,16 +83,10 @@ async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() ->
|
|||||||
let codex = builder.build(&server).await?.codex;
|
let codex = builder.build(&server).await?.codex;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "do not compress".into(),
|
||||||
text: "do not compress".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::sandboxing::SandboxPermissions;
|
use codex_core::sandboxing::SandboxPermissions;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
|
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
|
||||||
use codex_protocol::models::FileSystemPermissions;
|
use codex_protocol::models::FileSystemPermissions;
|
||||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||||
@@ -15,6 +19,7 @@ use codex_protocol::protocol::ExecApprovalRequestEvent;
|
|||||||
use codex_protocol::protocol::GranularApprovalConfig;
|
use codex_protocol::protocol::GranularApprovalConfig;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -175,31 +180,28 @@ async fn submit_turn(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.cwd.path());
|
turn_permission_fields(permission_profile, test.cwd.path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
#![cfg(target_os = "macos")]
|
#![cfg(target_os = "macos")]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::ApprovalsReviewer;
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::FileSystemPermissions;
|
use codex_protocol::models::FileSystemPermissions;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
@@ -12,6 +16,7 @@ use codex_protocol::protocol::AskForApproval;
|
|||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||||
@@ -144,31 +149,28 @@ async fn submit_turn(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
turn_permission_fields(permission_profile, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
approval_policy: Some(approval_policy),
|
approval_policy: Some(approval_policy),
|
||||||
approvals_reviewer,
|
approvals_reviewer,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use anyhow::Result;
|
|||||||
use codex_config::types::ToolSuggestDisabledTool;
|
use codex_config::types::ToolSuggestDisabledTool;
|
||||||
use codex_config::types::ToolSuggestDiscoverable;
|
use codex_config::types::ToolSuggestDiscoverable;
|
||||||
use codex_config::types::ToolSuggestDiscoverableType;
|
use codex_config::types::ToolSuggestDiscoverableType;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
|
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
@@ -213,21 +214,18 @@ async fn start_gated_step_preparation(test: &TestCodex, server: &MockServer) ->
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "prepare MCP and plugin recommendations".to_string(),
|
text: "prepare MCP and plugin recommendations".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let fs = test.fs();
|
let fs = test.fs();
|
||||||
@@ -262,15 +260,12 @@ async fn start_install_turn(test: &TestCodex, prompt: &str) -> Result<Elicitatio
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.to_string(),
|
text: prompt.to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: ThreadSettingsOverrides {
|
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
@@ -283,8 +278,8 @@ async fn start_install_turn(test: &TestCodex, prompt: &str) -> Result<Elicitatio
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(wait_for_event_match(&test.codex, |event| match event {
|
Ok(wait_for_event_match(&test.codex, |event| match event {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use core_test_support::test_codex::local_selections;
|
use core_test_support::test_codex::local_selections;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ use codex_protocol::models::PermissionProfile;
|
|||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::request_user_input::RequestUserInputAnswer;
|
use codex_protocol::request_user_input::RequestUserInputAnswer;
|
||||||
use codex_protocol::request_user_input::RequestUserInputResponse;
|
use codex_protocol::request_user_input::RequestUserInputResponse;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -130,15 +132,12 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "please confirm".into(),
|
text: "please confirm".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.abs())),
|
environments: Some(local_selections(cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
@@ -152,8 +151,8 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let request = wait_for_event_match(&codex, |event| match event {
|
let request = wait_for_event_match(&codex, |event| match event {
|
||||||
@@ -275,15 +274,12 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "please confirm".into(),
|
text: "please confirm".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.abs())),
|
environments: Some(local_selections(cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
@@ -297,8 +293,8 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let request = wait_for_event_match(&codex, |event| match event {
|
let request = wait_for_event_match(&codex, |event| match event {
|
||||||
@@ -379,23 +375,20 @@ where
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "please confirm".into(),
|
text: "please confirm".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.abs())),
|
environments: Some(local_selections(cwd.abs())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(collaboration_mode),
|
collaboration_mode: Some(collaboration_mode),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|||||||
@@ -3,11 +3,15 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ResponseMock;
|
use core_test_support::responses::ResponseMock;
|
||||||
use core_test_support::responses::ResponsesRequest;
|
use core_test_support::responses::ResponsesRequest;
|
||||||
@@ -157,30 +161,27 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()>
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::workspace_write(), cwd.as_path());
|
turn_permission_fields(PermissionProfile::workspace_write(), cwd.as_path());
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::OnRequest),
|
approval_policy: Some(AskForApproval::OnRequest),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let turn_started = wait_for_event_result(test, "turn started", |event| {
|
let turn_started = wait_for_event_result(test, "turn started", |event| {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_extension_api::ExtensionRegistry;
|
use codex_extension_api::ExtensionRegistry;
|
||||||
use codex_extension_api::ExtensionRegistryBuilder;
|
use codex_extension_api::ExtensionRegistryBuilder;
|
||||||
@@ -248,22 +249,16 @@ async fn responses_lite_prepares_images() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![
|
||||||
items: vec![
|
UserInput::Image {
|
||||||
UserInput::Image {
|
image_url: image_url.to_string(),
|
||||||
image_url: image_url.to_string(),
|
detail: Some(ImageDetail::Original),
|
||||||
detail: Some(ImageDetail::Original),
|
},
|
||||||
},
|
UserInput::Image {
|
||||||
UserInput::Image {
|
image_url: remote_image_url.to_string(),
|
||||||
image_url: remote_image_url.to_string(),
|
detail: Some(ImageDetail::High),
|
||||||
detail: Some(ImageDetail::High),
|
},
|
||||||
},
|
]))
|
||||||
],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::ByteRange;
|
use codex_protocol::user_input::ByteRange;
|
||||||
use codex_protocol::user_input::TextElement;
|
use codex_protocol::user_input::TextElement;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
@@ -40,16 +41,10 @@ async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> {
|
|||||||
)];
|
)];
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Record some messages".into(),
|
||||||
text: "Record some messages".into(),
|
text_elements: text_elements.clone(),
|
||||||
text_elements: text_elements.clone(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -101,16 +96,10 @@ async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()>
|
|||||||
mount_sse_once(&server, initial_sse).await;
|
mount_sse_once(&server, initial_sse).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Record reasoning messages".into(),
|
||||||
text: "Record reasoning messages".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -164,16 +153,10 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
|||||||
let initial_mock = mount_sse_once(&server, initial_sse).await;
|
let initial_mock = mount_sse_once(&server, initial_sse).await;
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Record initial instructions".into(),
|
||||||
text: "Record initial instructions".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
let initial_body = initial_mock.single_request().body_json();
|
let initial_body = initial_mock.single_request().body_json();
|
||||||
@@ -206,16 +189,10 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
|||||||
let resumed = resume_builder.restart(&server, &initial).await?;
|
let resumed = resume_builder.restart(&server, &initial).await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Resume with different model".into(),
|
||||||
text: "Resume with different model".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -224,16 +201,10 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> {
|
|||||||
|
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Second turn after resume".into(),
|
||||||
text: "Second turn after resume".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -291,16 +262,10 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Record initial instructions".into(),
|
||||||
text: "Record initial instructions".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
let _ = initial_mock.single_request();
|
let _ = initial_mock.single_request();
|
||||||
@@ -321,7 +286,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
|
|||||||
let resumed = resume_builder.restart(&server, &initial).await?;
|
let resumed = resume_builder.restart(&server, &initial).await?;
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&resumed.codex,
|
&resumed.codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
model: Some("gpt-5.4".to_string()),
|
model: Some("gpt-5.4".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -329,16 +294,10 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
|
|||||||
.await?;
|
.await?;
|
||||||
resumed
|
resumed
|
||||||
.codex
|
.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "first turn after override".into(),
|
||||||
text: "first turn after override".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&resumed.codex, |event| {
|
wait_for_event(&resumed.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use codex_core::CodexThread;
|
use codex_core::CodexThread;
|
||||||
use codex_core::REVIEW_PROMPT;
|
use codex_core::REVIEW_PROMPT;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_core::config::Constrained;
|
use codex_core::config::Constrained;
|
||||||
use codex_core::find_thread_path_by_id_str;
|
use codex_core::find_thread_path_by_id_str;
|
||||||
@@ -1125,16 +1126,10 @@ async fn review_history_surfaces_in_parent_session() {
|
|||||||
// 2) Continue in the parent session; request input must not include any review items.
|
// 2) Continue in the parent session; request input must not include any review items.
|
||||||
let followup = "back to parent".to_string();
|
let followup = "back to parent".to_string();
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: followup.clone(),
|
||||||
text: followup.clone(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||||
@@ -1240,7 +1235,7 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() {
|
|||||||
|
|
||||||
core_test_support::submit_thread_settings(
|
core_test_support::submit_thread_settings(
|
||||||
&codex,
|
&codex,
|
||||||
codex_protocol::protocol::ThreadSettingsOverrides {
|
ThreadSettingsOverrides {
|
||||||
environments: Some(local_selections(repo_path.to_path_buf().abs())),
|
environments: Some(local_selections(repo_path.to_path_buf().abs())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use codex_config::types::McpServerConfig;
|
|||||||
use codex_config::types::McpServerEnvVar;
|
use codex_config::types::McpServerEnvVar;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_config::types::OAuthCredentialsStoreMode;
|
use codex_config::types::OAuthCredentialsStoreMode;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_exec_server::Environment;
|
use codex_exec_server::Environment;
|
||||||
@@ -37,7 +38,10 @@ use codex_models_manager::manager::RefreshStrategy;
|
|||||||
use codex_utils_path_uri::LegacyAppPathString;
|
use codex_utils_path_uri::LegacyAppPathString;
|
||||||
|
|
||||||
use codex_history::RolloutItem;
|
use codex_history::RolloutItem;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::ReasoningSummary;
|
use codex_protocol::config_types::ReasoningSummary;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
@@ -55,6 +59,7 @@ use codex_protocol::protocol::McpStartupFailureReason;
|
|||||||
use codex_protocol::protocol::McpStartupStatus;
|
use codex_protocol::protocol::McpStartupStatus;
|
||||||
use codex_protocol::protocol::McpToolCallBeginEvent;
|
use codex_protocol::protocol::McpToolCallBeginEvent;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use codex_utils_cargo_bin::cargo_bin;
|
use codex_utils_cargo_bin::cargo_bin;
|
||||||
use codex_utils_path_uri::PathUri;
|
use codex_utils_path_uri::PathUri;
|
||||||
@@ -115,7 +120,7 @@ fn assert_wall_time_header(output: &str) {
|
|||||||
assert_eq!(marker, "Output:");
|
assert_eq!(marker, "Output:");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_only_user_turn(fixture: &TestCodex, text: impl Into<String>) -> Op {
|
fn read_only_user_turn(fixture: &TestCodex, text: impl Into<String>) -> TurnInputRequest {
|
||||||
read_only_user_turn_with_model(fixture, text, fixture.session_configured.model.clone())
|
read_only_user_turn_with_model(fixture, text, fixture.session_configured.model.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +128,11 @@ fn read_only_user_turn_with_model(
|
|||||||
fixture: &TestCodex,
|
fixture: &TestCodex,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
model: String,
|
model: String,
|
||||||
) -> Op {
|
) -> TurnInputRequest {
|
||||||
user_turn_with_permission_profile(fixture, text, model, PermissionProfile::read_only())
|
user_turn_with_permission_profile(fixture, text, model, PermissionProfile::read_only())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auto_approved_user_turn(fixture: &TestCodex, text: impl Into<String>) -> Op {
|
fn auto_approved_user_turn(fixture: &TestCodex, text: impl Into<String>) -> TurnInputRequest {
|
||||||
user_turn_with_permission_profile(
|
user_turn_with_permission_profile(
|
||||||
fixture,
|
fixture,
|
||||||
text,
|
text,
|
||||||
@@ -141,33 +146,28 @@ fn user_turn_with_permission_profile(
|
|||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
model: String,
|
model: String,
|
||||||
permission_profile: PermissionProfile,
|
permission_profile: PermissionProfile,
|
||||||
) -> Op {
|
) -> TurnInputRequest {
|
||||||
let cwd = fixture.config.cwd.clone();
|
let cwd = fixture.config.cwd.clone();
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(permission_profile, cwd.as_path());
|
turn_permission_fields(permission_profile, cwd.as_path());
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.into(),
|
||||||
text: text.into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
responsesapi_client_metadata: None,
|
sandbox_policy: Some(sandbox_policy),
|
||||||
additional_context: Default::default(),
|
permission_profile,
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
mode: ModeKind::Default,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
settings: Settings {
|
||||||
permission_profile,
|
model,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
reasoning_effort: None,
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
developer_instructions: None,
|
||||||
settings: codex_protocol::config_types::Settings {
|
},
|
||||||
model,
|
}),
|
||||||
reasoning_effort: None,
|
..Default::default()
|
||||||
developer_instructions: None,
|
})
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -390,7 +390,7 @@ async fn call_structured_tool(
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(fixture, "call the requested rmcp tool"))
|
.start_or_steer_turn(read_only_user_turn(fixture, "call the requested rmcp tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&fixture.codex, |ev| {
|
wait_for_event(&fixture.codex, |ev| {
|
||||||
@@ -540,7 +540,7 @@ async fn mcp_namespace_instructions_are_preserved_without_hiding_tools() -> anyh
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "show the bounded MCP tools"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "show the bounded MCP tools"))
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&fixture.codex, |event| {
|
wait_for_event(&fixture.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnComplete(_))
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
@@ -650,7 +650,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin_event = wait_for_event(&fixture.codex, |ev| {
|
let begin_event = wait_for_event(&fixture.codex, |ev| {
|
||||||
@@ -951,7 +951,7 @@ async fn modern_mcp_pagination_preserves_valid_tools_and_rejects_oversized_curso
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"show the paginated MCP tools",
|
"show the paginated MCP tools",
|
||||||
))
|
))
|
||||||
@@ -1159,7 +1159,7 @@ async fn interrupt_during_mcp_startup_preserves_user_input_in_history(
|
|||||||
let prompt = "keep this interrupted prompt in conversation history";
|
let prompt = "keep this interrupted prompt in conversation history";
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, prompt))
|
.start_or_steer_turn(read_only_user_turn(&fixture, prompt))
|
||||||
.await?;
|
.await?;
|
||||||
wait_for_event(&fixture.codex, |event| {
|
wait_for_event(&fixture.codex, |event| {
|
||||||
matches!(event, EventMsg::TurnStarted(_))
|
matches!(event, EventMsg::TurnStarted(_))
|
||||||
@@ -1546,7 +1546,7 @@ async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::
|
|||||||
// Keep this baseline on the mutable sync tool so read-only hints do not
|
// Keep this baseline on the mutable sync tool so read-only hints do not
|
||||||
// make the call parallel-safe. Bypass read-only turn permissions so
|
// make the call parallel-safe. Bypass read-only turn permissions so
|
||||||
// approval behavior does not block the scheduling assertion.
|
// approval behavior does not block the scheduling assertion.
|
||||||
.submit(auto_approved_user_turn(
|
.start_or_steer_turn(auto_approved_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp sync tool twice",
|
"call the rmcp sync tool twice",
|
||||||
))
|
))
|
||||||
@@ -1684,7 +1684,7 @@ async fn stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in()
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp sync_readonly tool twice",
|
"call the rmcp sync_readonly tool twice",
|
||||||
))
|
))
|
||||||
@@ -1777,7 +1777,7 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
|
|||||||
// Exercise the server opt-in with the mutable sync tool rather than the
|
// Exercise the server opt-in with the mutable sync tool rather than the
|
||||||
// read-only sync_readonly tool. Bypass read-only turn permissions so
|
// read-only sync_readonly tool. Bypass read-only turn permissions so
|
||||||
// approval behavior does not block the scheduling assertion.
|
// approval behavior does not block the scheduling assertion.
|
||||||
.submit(auto_approved_user_turn(
|
.start_or_steer_turn(auto_approved_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp sync tool twice",
|
"call the rmcp sync tool twice",
|
||||||
))
|
))
|
||||||
@@ -1857,7 +1857,7 @@ async fn stdio_encrypted_content_responses_round_trip() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp encrypted output tool",
|
"call the rmcp encrypted output tool",
|
||||||
))
|
))
|
||||||
@@ -1956,7 +1956,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "call the rmcp image tool"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "call the rmcp image tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Wait for tool begin/end and final completion.
|
// Wait for tool begin/end and final completion.
|
||||||
@@ -2113,7 +2113,7 @@ async fn stdio_image_responses_resize_large_image() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp image_scenario tool",
|
"call the rmcp image_scenario tool",
|
||||||
))
|
))
|
||||||
@@ -2201,7 +2201,7 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp image_scenario tool",
|
"call the rmcp image_scenario tool",
|
||||||
))
|
))
|
||||||
@@ -2362,7 +2362,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn_with_model(
|
.start_or_steer_turn(read_only_user_turn_with_model(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp image tool",
|
"call the rmcp image tool",
|
||||||
text_only_model_slug.to_string(),
|
text_only_model_slug.to_string(),
|
||||||
@@ -2463,7 +2463,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin_event = wait_for_event(&fixture.codex, |ev| {
|
let begin_event = wait_for_event(&fixture.codex, |ev| {
|
||||||
@@ -2588,7 +2588,7 @@ async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Resu
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&fixture.codex, |ev| {
|
wait_for_event(&fixture.codex, |ev| {
|
||||||
@@ -2684,7 +2684,7 @@ async fn remote_stdio_env_var_source_does_not_copy_local_env() -> anyhow::Result
|
|||||||
|
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
.start_or_steer_turn(read_only_user_turn(&fixture, "call the rmcp echo tool"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&fixture.codex, |ev| {
|
wait_for_event(&fixture.codex, |ev| {
|
||||||
@@ -2886,7 +2886,7 @@ async fn streamable_http_tool_call_round_trip(with_headers_helper: bool) -> anyh
|
|||||||
// Phase 4: submit the user turn that should trigger the MCP tool call.
|
// Phase 4: submit the user turn that should trigger the MCP tool call.
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp streamable http echo tool",
|
"call the rmcp streamable http echo tool",
|
||||||
))
|
))
|
||||||
@@ -3317,7 +3317,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
|||||||
store_lock.try_lock()?;
|
store_lock.try_lock()?;
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"continue while OAuth credentials are locked",
|
"continue while OAuth credentials are locked",
|
||||||
))
|
))
|
||||||
@@ -3397,7 +3397,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
|||||||
.await;
|
.await;
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"continue while a newly discovered OAuth server is starting",
|
"continue while a newly discovered OAuth server is starting",
|
||||||
))
|
))
|
||||||
@@ -3466,7 +3466,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
|||||||
// Phase 6: submit the user turn that should invoke the OAuth-backed tool.
|
// Phase 6: submit the user turn that should invoke the OAuth-backed tool.
|
||||||
fixture
|
fixture
|
||||||
.codex
|
.codex
|
||||||
.submit(read_only_user_turn(
|
.start_or_steer_turn(read_only_user_turn(
|
||||||
&fixture,
|
&fixture,
|
||||||
"call the rmcp streamable http oauth echo tool",
|
"call the rmcp streamable http oauth echo tool",
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::RolloutBudgetConfig;
|
use codex_core::config::RolloutBudgetConfig;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_model_provider_info::built_in_model_providers;
|
use codex_model_provider_info::built_in_model_providers;
|
||||||
@@ -141,16 +142,10 @@ async fn invalid_provider_rollout_budget_units_fail_without_retry() -> Result<()
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "reject invalid provider budget units".to_string(),
|
||||||
text: "reject invalid provider budget units".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::Error(error) =
|
let EventMsg::Error(error) =
|
||||||
@@ -312,16 +307,10 @@ async fn exhausted_budget_fails_current_and_later_turns() -> Result<()> {
|
|||||||
|
|
||||||
for prompt in ["exhaust the budget", "try another turn"] {
|
for prompt in ["exhaust the budget", "try another turn"] {
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: prompt.to_string(),
|
||||||
text: prompt.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Ok;
|
use anyhow::Ok;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::Op;
|
|
||||||
use codex_protocol::protocol::SafetyBufferingEvent;
|
use codex_protocol::protocol::SafetyBufferingEvent;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -48,16 +48,10 @@ async fn emits_safety_buffering_from_response_metadata_with_the_header_fallback_
|
|||||||
|
|
||||||
let test = test_codex().build(&server).await?;
|
let test = test_codex().build(&server).await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Check this request".into(),
|
||||||
text: "Check this request".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let event = wait_for_event_match(&test.codex, |event| match event {
|
let event = wait_for_event_match(&test.codex, |event| match event {
|
||||||
@@ -103,16 +97,10 @@ async fn emits_safety_buffering_with_the_responses_api_model_without_header_gati
|
|||||||
|
|
||||||
let test = test_codex().build(&server).await?;
|
let test = test_codex().build(&server).await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Check this request".into(),
|
||||||
text: "Check this request".into(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let event = wait_for_event_match(&test.codex, |event| match event {
|
let event = wait_for_event_match(&test.codex, |event| match event {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
@@ -7,7 +11,7 @@ use codex_protocol::protocol::CodexErrorInfo;
|
|||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ModelRerouteReason;
|
use codex_protocol::protocol::ModelRerouteReason;
|
||||||
use codex_protocol::protocol::ModelVerification;
|
use codex_protocol::protocol::ModelVerification;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_assistant_message;
|
use core_test_support::responses::ev_assistant_message;
|
||||||
use core_test_support::responses::ev_function_call;
|
use core_test_support::responses::ev_function_call;
|
||||||
@@ -35,33 +39,28 @@ const TRUSTED_ACCESS_FOR_CYBER_VERIFICATION: &str = "trusted_access_for_cyber";
|
|||||||
const CYBER_POLICY_MESSAGE: &str =
|
const CYBER_POLICY_MESSAGE: &str =
|
||||||
"This request has been flagged for potentially high-risk cyber activity.";
|
"This request has been flagged for potentially high-risk cyber activity.";
|
||||||
|
|
||||||
fn disabled_text_turn(test: &TestCodex, text: &str) -> Op {
|
fn disabled_text_turn(test: &TestCodex, text: &str) -> TurnInputRequest {
|
||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, test.cwd_path());
|
turn_permission_fields(PermissionProfile::Disabled, test.cwd_path());
|
||||||
Op::UserInput {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: text.to_string(),
|
||||||
text: text.to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}])
|
||||||
}],
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
final_output_json_schema: None,
|
environments: Some(local_selections(test.config.cwd.clone())),
|
||||||
responsesapi_client_metadata: None,
|
approval_policy: Some(AskForApproval::Never),
|
||||||
additional_context: Default::default(),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
permission_profile,
|
||||||
environments: Some(local_selections(test.config.cwd.clone())),
|
collaboration_mode: Some(CollaborationMode {
|
||||||
approval_policy: Some(AskForApproval::Never),
|
mode: ModeKind::Default,
|
||||||
sandbox_policy: Some(sandbox_policy),
|
settings: Settings {
|
||||||
permission_profile,
|
model: test.session_configured.model.clone(),
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
developer_instructions: None,
|
||||||
settings: codex_protocol::config_types::Settings {
|
},
|
||||||
model: test.session_configured.model.clone(),
|
}),
|
||||||
reasoning_effort: test.config.model_reasoning_effort.clone(),
|
..Default::default()
|
||||||
developer_instructions: None,
|
})
|
||||||
},
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -77,7 +76,7 @@ async fn openai_model_header_mismatch_emits_warning_event() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger safety check"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger safety check"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let reroute = wait_for_event(&test.codex, |event| {
|
let reroute = wait_for_event(&test.codex, |event| {
|
||||||
@@ -125,7 +124,7 @@ async fn cyber_policy_response_emits_typed_error_without_retry() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger cyber policy error"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger cyber policy error"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let error = wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await;
|
let error = wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await;
|
||||||
@@ -164,7 +163,7 @@ async fn response_model_field_mismatch_emits_warning_when_header_matches_request
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger response model check"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger response model check"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let reroute = wait_for_event(&test.codex, |event| {
|
let reroute = wait_for_event(&test.codex, |event| {
|
||||||
@@ -234,7 +233,7 @@ async fn openai_model_header_mismatch_only_emits_one_warning_per_turn() -> Resul
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger follow-up turn"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger follow-up turn"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut warning_count = 0;
|
let mut warning_count = 0;
|
||||||
@@ -272,7 +271,7 @@ async fn openai_model_header_casing_only_mismatch_does_not_warn() -> Result<()>
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger casing check"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger casing check"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut reroute_count = 0;
|
let mut reroute_count = 0;
|
||||||
@@ -315,7 +314,7 @@ async fn model_verification_emits_structured_event_without_reroute_or_warning()
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(&test, "trigger model verification"))
|
.start_or_steer_turn(disabled_text_turn(&test, "trigger model verification"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut verification_count = 0;
|
let mut verification_count = 0;
|
||||||
@@ -391,7 +390,7 @@ async fn model_verification_only_emits_once_per_turn() -> Result<()> {
|
|||||||
let test = builder.build(&server).await?;
|
let test = builder.build(&server).await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(disabled_text_turn(
|
.start_or_steer_turn(disabled_text_turn(
|
||||||
&test,
|
&test,
|
||||||
"trigger follow-up model verification",
|
"trigger follow-up model verification",
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use anyhow::Result;
|
|||||||
use codex_config::types::McpServerConfig;
|
use codex_config::types::McpServerConfig;
|
||||||
use codex_config::types::McpServerTransportConfig;
|
use codex_config::types::McpServerTransportConfig;
|
||||||
use codex_core::StartThreadOptions;
|
use codex_core::StartThreadOptions;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
use codex_core::config::Config;
|
use codex_core::config::Config;
|
||||||
use codex_extension_api::ExtensionData;
|
use codex_extension_api::ExtensionData;
|
||||||
use codex_extension_api::ExtensionRegistryBuilder;
|
use codex_extension_api::ExtensionRegistryBuilder;
|
||||||
@@ -598,16 +599,10 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
|
|||||||
let mut builder = configured_builder(apps_server.chatgpt_base_url.clone());
|
let mut builder = configured_builder(apps_server.chatgpt_base_url.clone());
|
||||||
let test = builder.build_with_auto_env(&server).await?;
|
let test = builder.build_with_auto_env(&server).await?;
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Find the calendar create tool".to_string(),
|
||||||
text: "Find the calendar create tool".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::McpToolCallBegin(begin) = wait_for_event(&test.codex, |event| {
|
let EventMsg::McpToolCallBegin(begin) = wait_for_event(&test.codex, |event| {
|
||||||
@@ -1157,16 +1152,10 @@ async fn tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_call() -
|
|||||||
test.session_configured = new_thread.session_configured;
|
test.session_configured = new_thread.session_configured;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Use the automation tool".to_string(),
|
||||||
text: "Use the automation tool".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::DynamicToolCallRequest(request) = wait_for_event(&test.codex, |event| {
|
let EventMsg::DynamicToolCallRequest(request) = wait_for_event(&test.codex, |event| {
|
||||||
@@ -1489,16 +1478,10 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result<
|
|||||||
wait_for_mcp_server(&test.codex, "rmcp").await?;
|
wait_for_mcp_server(&test.codex, "rmcp").await?;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Find the rmcp echo tool and call it.".to_string(),
|
||||||
text: "Find the rmcp echo tool and call it.".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let EventMsg::McpToolCallEnd(end) = wait_for_event(&test.codex, |event| {
|
let EventMsg::McpToolCallEnd(end) = wait_for_event(&test.codex, |event| {
|
||||||
@@ -1822,16 +1805,10 @@ async fn tool_search_matches_dynamic_tools_by_name_description_namespace_and_sch
|
|||||||
test.session_configured = new_thread.session_configured;
|
test.session_configured = new_thread.session_configured;
|
||||||
|
|
||||||
test.codex
|
test.codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
items: vec![UserInput::Text {
|
text: "Search for the dynamic tool".to_string(),
|
||||||
text: "Search for the dynamic tool".to_string(),
|
text_elements: Vec::new(),
|
||||||
text_elements: Vec::new(),
|
}]))
|
||||||
}],
|
|
||||||
final_output_json_schema: None,
|
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event(&test.codex, |event| {
|
wait_for_event(&test.codex, |event| {
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use codex_core::TurnInputRequest;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use codex_core::shell::get_shell_by_model_provided_path;
|
use codex_core::shell::get_shell_by_model_provided_path;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::ModeKind;
|
||||||
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||||
use codex_protocol::protocol::ExecCommandEndEvent;
|
use codex_protocol::protocol::ExecCommandEndEvent;
|
||||||
use codex_protocol::protocol::Op;
|
use codex_protocol::protocol::Op;
|
||||||
|
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
use core_test_support::responses::ev_assistant_message;
|
use core_test_support::responses::ev_assistant_message;
|
||||||
use core_test_support::responses::ev_completed;
|
use core_test_support::responses::ev_completed;
|
||||||
@@ -162,30 +167,27 @@ async fn run_snapshot_command_with_options(
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run unified exec with shell snapshot".into(),
|
text: "run unified exec with shell snapshot".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin = wait_for_event_match(&codex, |ev| match ev {
|
let begin = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -260,30 +262,27 @@ async fn run_shell_command_snapshot_with_options(
|
|||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
|
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "run shell_command with shell snapshot".into(),
|
text: "run shell_command with shell snapshot".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let begin = wait_for_event_match(&codex, |ev| match ev {
|
let begin = wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -339,30 +338,27 @@ async fn run_tool_turn_on_harness(
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: prompt.into(),
|
text: prompt.into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd)),
|
environments: Some(local_selections(cwd)),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model: session_model,
|
model: session_model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wait_for_event_match(&codex, |ev| match ev {
|
wait_for_event_match(&codex, |ev| match ev {
|
||||||
@@ -582,30 +578,27 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> {
|
|||||||
let (sandbox_policy, permission_profile) =
|
let (sandbox_policy, permission_profile) =
|
||||||
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
turn_permission_fields(PermissionProfile::Disabled, cwd.as_path());
|
||||||
codex
|
codex
|
||||||
.submit(Op::UserInput {
|
.start_or_steer_turn(
|
||||||
items: vec![UserInput::Text {
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
text: "apply patch via shell_command with snapshot".into(),
|
text: "apply patch via shell_command with snapshot".into(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}])
|
||||||
final_output_json_schema: None,
|
.with_thread_settings(ThreadSettingsOverrides {
|
||||||
responsesapi_client_metadata: None,
|
|
||||||
additional_context: Default::default(),
|
|
||||||
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
|
|
||||||
environments: Some(local_selections(cwd.clone())),
|
environments: Some(local_selections(cwd.clone())),
|
||||||
approval_policy: Some(AskForApproval::Never),
|
approval_policy: Some(AskForApproval::Never),
|
||||||
sandbox_policy: Some(sandbox_policy),
|
sandbox_policy: Some(sandbox_policy),
|
||||||
permission_profile,
|
permission_profile,
|
||||||
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
|
collaboration_mode: Some(CollaborationMode {
|
||||||
mode: codex_protocol::config_types::ModeKind::Default,
|
mode: ModeKind::Default,
|
||||||
settings: codex_protocol::config_types::Settings {
|
settings: Settings {
|
||||||
model,
|
model,
|
||||||
reasoning_effort: None,
|
reasoning_effort: None,
|
||||||
developer_instructions: None,
|
developer_instructions: None,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut saw_patch_begin = false;
|
let mut saw_patch_begin = false;
|
||||||
|
|||||||
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user