mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)
## Why `argument-comment-lint` was green in CI even though the repo still had many uncommented literal arguments. The main gap was target coverage: the repo wrapper did not force Cargo to inspect test-only call sites, so examples like the `latest_session_lookup_params(true, ...)` tests in `codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path. This change cleans up the existing backlog, makes the default repo lint path cover all Cargo targets, and starts rolling that stricter CI enforcement out on the platform where it is currently validated. ## What changed - mechanically fixed existing `argument-comment-lint` violations across the `codex-rs` workspace, including tests, examples, and benches - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and `tools/argument-comment-lint/run.sh` so non-`--fix` runs default to `--all-targets` unless the caller explicitly narrows the target set - fixed both wrappers so forwarded cargo arguments after `--` are preserved with a single separator - documented the new default behavior in `tools/argument-comment-lint/README.md` - updated `rust-ci` so the macOS lint lane keeps the plain wrapper invocation and therefore enforces `--all-targets`, while Linux and Windows temporarily pass `-- --lib --bins` That temporary CI split keeps the stricter all-targets check where it is already cleaned up, while leaving room to finish the remaining Linux- and Windows-specific target-gated cleanup before enabling `--all-targets` on those runners. The Linux and Windows failures on the intermediate revision were caused by the wrapper forwarding bug, not by additional lint findings in those lanes. ## Validation - `bash -n tools/argument-comment-lint/run.sh` - `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh` - shell-level wrapper forwarding check for `-- --lib --bins` - shell-level wrapper forwarding check for `-- --tests` - `just argument-comment-lint` - `cargo test` in `tools/argument-comment-lint` - `cargo test -p codex-terminal-detection` ## Follow-up - Clean up remaining Linux-only target-gated callsites, then switch the Linux lint lane back to the plain wrapper invocation. - Clean up remaining Windows-only target-gated callsites, then switch the Windows lint lane back to the plain wrapper invocation.
This commit is contained in:
@@ -289,7 +289,7 @@ async fn spawn_agent_errors_when_manager_dropped() {
|
||||
let control = AgentControl::default();
|
||||
let (_home, config) = test_config().await;
|
||||
let err = control
|
||||
.spawn_agent(config, text_input("hello"), None)
|
||||
.spawn_agent(config, text_input("hello"), /*session_source*/ None)
|
||||
.await
|
||||
.expect_err("spawn_agent should fail without a manager");
|
||||
assert_eq!(
|
||||
@@ -423,7 +423,7 @@ async fn send_inter_agent_communication_without_turn_queues_message_without_trig
|
||||
AgentPath::try_from("/root/worker").expect("agent path"),
|
||||
Vec::new(),
|
||||
"hello from tests".to_string(),
|
||||
false,
|
||||
/*trigger_turn*/ false,
|
||||
);
|
||||
|
||||
let submission_id = harness
|
||||
@@ -535,7 +535,11 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let thread_id = harness
|
||||
.control
|
||||
.spawn_agent(harness.config.clone(), text_input("spawned"), None)
|
||||
.spawn_agent(
|
||||
harness.config.clone(),
|
||||
text_input("spawned"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
let _thread = harness
|
||||
@@ -830,12 +834,20 @@ async fn spawn_agent_respects_max_threads_limit() {
|
||||
.expect("start thread");
|
||||
|
||||
let first_agent_id = control
|
||||
.spawn_agent(config.clone(), text_input("hello"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("hello"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
|
||||
let err = control
|
||||
.spawn_agent(config, text_input("hello again"), None)
|
||||
.spawn_agent(
|
||||
config,
|
||||
text_input("hello again"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect_err("spawn_agent should respect max threads");
|
||||
let CodexErr::AgentLimitReached {
|
||||
@@ -871,7 +883,11 @@ async fn spawn_agent_releases_slot_after_shutdown() {
|
||||
let control = manager.agent_control();
|
||||
|
||||
let first_agent_id = control
|
||||
.spawn_agent(config.clone(), text_input("hello"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("hello"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
let _ = control
|
||||
@@ -880,7 +896,11 @@ async fn spawn_agent_releases_slot_after_shutdown() {
|
||||
.expect("shutdown agent");
|
||||
|
||||
let second_agent_id = control
|
||||
.spawn_agent(config.clone(), text_input("hello again"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("hello again"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed after shutdown");
|
||||
let _ = control
|
||||
@@ -909,12 +929,20 @@ async fn spawn_agent_limit_shared_across_clones() {
|
||||
let cloned = control.clone();
|
||||
|
||||
let first_agent_id = cloned
|
||||
.spawn_agent(config.clone(), text_input("hello"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("hello"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
|
||||
let err = control
|
||||
.spawn_agent(config, text_input("hello again"), None)
|
||||
.spawn_agent(
|
||||
config,
|
||||
text_input("hello again"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect_err("spawn_agent should respect shared guard");
|
||||
let CodexErr::AgentLimitReached { max_threads } = err else {
|
||||
@@ -947,7 +975,11 @@ async fn resume_agent_respects_max_threads_limit() {
|
||||
let control = manager.agent_control();
|
||||
|
||||
let resumable_id = control
|
||||
.spawn_agent(config.clone(), text_input("hello"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("hello"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed");
|
||||
let _ = control
|
||||
@@ -956,7 +988,11 @@ async fn resume_agent_respects_max_threads_limit() {
|
||||
.expect("shutdown resumable thread");
|
||||
|
||||
let active_id = control
|
||||
.spawn_agent(config.clone(), text_input("occupy"), None)
|
||||
.spawn_agent(
|
||||
config.clone(),
|
||||
text_input("occupy"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("spawn_agent should succeed for active slot");
|
||||
|
||||
@@ -1002,7 +1038,7 @@ async fn resume_agent_releases_slot_after_resume_failure() {
|
||||
.expect_err("resume should fail for missing rollout path");
|
||||
|
||||
let resumed_id = control
|
||||
.spawn_agent(config, text_input("hello"), None)
|
||||
.spawn_agent(config, text_input("hello"), /*session_source*/ None)
|
||||
.await
|
||||
.expect("spawn should succeed after failed resume");
|
||||
let _ = control
|
||||
@@ -1140,7 +1176,7 @@ async fn multi_agent_v2_completion_ignores_dead_direct_parent() {
|
||||
AgentPath::root(),
|
||||
Vec::new(),
|
||||
"done".to_string(),
|
||||
true,
|
||||
/*trigger_turn*/ true,
|
||||
)
|
||||
));
|
||||
assert!(!has_subagent_notification(&root_history_items));
|
||||
@@ -1203,7 +1239,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
|
||||
worker_path.clone(),
|
||||
Vec::new(),
|
||||
expected_message.clone(),
|
||||
false,
|
||||
/*trigger_turn*/ false,
|
||||
),
|
||||
},
|
||||
);
|
||||
@@ -1238,7 +1274,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() {
|
||||
AgentPath::root(),
|
||||
Vec::new(),
|
||||
expected_message,
|
||||
false,
|
||||
/*trigger_turn*/ false,
|
||||
)
|
||||
));
|
||||
}
|
||||
@@ -1259,7 +1295,7 @@ async fn completion_watcher_notifies_parent_when_child_is_missing() {
|
||||
agent_role: Some("explorer".to_string()),
|
||||
})),
|
||||
child_thread_id.to_string(),
|
||||
None,
|
||||
/*child_agent_path*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(wait_for_subagent_notification(&parent_thread).await, true);
|
||||
@@ -1520,7 +1556,11 @@ async fn resume_agent_from_rollout_reads_archived_rollout_path() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let child_thread_id = harness
|
||||
.control
|
||||
.spawn_agent(harness.config.clone(), text_input("hello"), None)
|
||||
.spawn_agent(
|
||||
harness.config.clone(),
|
||||
text_input("hello"),
|
||||
/*session_source*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("child spawn should succeed");
|
||||
|
||||
|
||||
@@ -16,11 +16,26 @@ fn agent_metadata(thread_id: ThreadId) -> AgentMetadata {
|
||||
|
||||
#[test]
|
||||
fn format_agent_nickname_adds_ordinals_after_reset() {
|
||||
assert_eq!(format_agent_nickname("Plato", 0), "Plato");
|
||||
assert_eq!(format_agent_nickname("Plato", 1), "Plato the 2nd");
|
||||
assert_eq!(format_agent_nickname("Plato", 2), "Plato the 3rd");
|
||||
assert_eq!(format_agent_nickname("Plato", 10), "Plato the 11th");
|
||||
assert_eq!(format_agent_nickname("Plato", 20), "Plato the 21st");
|
||||
assert_eq!(
|
||||
format_agent_nickname("Plato", /*nickname_reset_count*/ 0),
|
||||
"Plato"
|
||||
);
|
||||
assert_eq!(
|
||||
format_agent_nickname("Plato", /*nickname_reset_count*/ 1),
|
||||
"Plato the 2nd"
|
||||
);
|
||||
assert_eq!(
|
||||
format_agent_nickname("Plato", /*nickname_reset_count*/ 2),
|
||||
"Plato the 3rd"
|
||||
);
|
||||
assert_eq!(
|
||||
format_agent_nickname("Plato", /*nickname_reset_count*/ 10),
|
||||
"Plato the 11th"
|
||||
);
|
||||
assert_eq!(
|
||||
format_agent_nickname("Plato", /*nickname_reset_count*/ 20),
|
||||
"Plato the 21st"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -39,7 +54,10 @@ fn thread_spawn_depth_increments_and_enforces_limit() {
|
||||
});
|
||||
let child_depth = next_thread_spawn_depth(&session_source);
|
||||
assert_eq!(child_depth, 2);
|
||||
assert!(exceeds_thread_spawn_depth_limit(child_depth, 1));
|
||||
assert!(exceeds_thread_spawn_depth_limit(
|
||||
child_depth,
|
||||
/*max_depth*/ 1
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -47,7 +65,9 @@ fn non_thread_spawn_subagents_default_to_depth_zero() {
|
||||
let session_source = SessionSource::SubAgent(SubAgentSource::Review);
|
||||
assert_eq!(session_depth(&session_source), 0);
|
||||
assert_eq!(next_thread_spawn_depth(&session_source), 1);
|
||||
assert!(!exceeds_thread_spawn_depth_limit(1, 1));
|
||||
assert!(!exceeds_thread_spawn_depth_limit(
|
||||
/*depth*/ 1, /*max_depth*/ 1
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -142,14 +162,18 @@ fn release_is_idempotent_for_registered_threads() {
|
||||
#[test]
|
||||
fn failed_spawn_keeps_nickname_marked_used() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
|
||||
let mut reservation = registry
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve slot");
|
||||
let agent_nickname = reservation
|
||||
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
|
||||
.expect("reserve agent name");
|
||||
assert_eq!(agent_nickname, "alpha");
|
||||
drop(reservation);
|
||||
|
||||
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
|
||||
let mut reservation = registry
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve slot");
|
||||
let agent_nickname = reservation
|
||||
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
|
||||
.expect("unused name should still be preferred");
|
||||
@@ -160,7 +184,7 @@ fn failed_spawn_keeps_nickname_marked_used() {
|
||||
fn agent_nickname_resets_used_pool_when_exhausted() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
let mut first = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve first slot");
|
||||
let first_name = first
|
||||
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
|
||||
@@ -170,7 +194,7 @@ fn agent_nickname_resets_used_pool_when_exhausted() {
|
||||
assert_eq!(first_name, "alpha");
|
||||
|
||||
let mut second = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve second slot");
|
||||
let second_name = second
|
||||
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
|
||||
@@ -188,7 +212,7 @@ fn released_nickname_stays_used_until_pool_reset() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
|
||||
let mut first = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve first slot");
|
||||
let first_name = first
|
||||
.reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None)
|
||||
@@ -200,7 +224,7 @@ fn released_nickname_stays_used_until_pool_reset() {
|
||||
registry.release_spawned_thread(first_id);
|
||||
|
||||
let mut second = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve second slot");
|
||||
let second_name = second
|
||||
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
|
||||
@@ -211,7 +235,7 @@ fn released_nickname_stays_used_until_pool_reset() {
|
||||
registry.release_spawned_thread(second_id);
|
||||
|
||||
let mut third = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve third slot");
|
||||
let third_name = third
|
||||
.reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None)
|
||||
@@ -230,7 +254,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
|
||||
let mut first = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve first slot");
|
||||
let first_name = first
|
||||
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
|
||||
@@ -241,7 +265,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
|
||||
registry.release_spawned_thread(first_id);
|
||||
|
||||
let mut second = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve second slot");
|
||||
let second_name = second
|
||||
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
|
||||
@@ -252,7 +276,7 @@ fn repeated_resets_advance_the_ordinal_suffix() {
|
||||
registry.release_spawned_thread(second_id);
|
||||
|
||||
let mut third = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve third slot");
|
||||
let third_name = third
|
||||
.reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None)
|
||||
@@ -282,7 +306,7 @@ fn register_root_thread_indexes_root_path() {
|
||||
fn reserved_agent_path_is_released_when_spawn_fails() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
let mut first = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve first slot");
|
||||
first
|
||||
.reserve_agent_path(&agent_path("/root/researcher"))
|
||||
@@ -290,7 +314,7 @@ fn reserved_agent_path_is_released_when_spawn_fails() {
|
||||
drop(first);
|
||||
|
||||
let mut second = registry
|
||||
.reserve_spawn_slot(None)
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve second slot");
|
||||
second
|
||||
.reserve_agent_path(&agent_path("/root/researcher"))
|
||||
@@ -301,7 +325,9 @@ fn reserved_agent_path_is_released_when_spawn_fails() {
|
||||
fn committed_agent_path_is_indexed_until_release() {
|
||||
let registry = Arc::new(AgentRegistry::default());
|
||||
let thread_id = ThreadId::new();
|
||||
let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot");
|
||||
let mut reservation = registry
|
||||
.reserve_spawn_slot(/*max_threads*/ None)
|
||||
.expect("reserve slot");
|
||||
reservation
|
||||
.reserve_agent_path(&agent_path("/root/researcher"))
|
||||
.expect("reserve path");
|
||||
|
||||
@@ -40,7 +40,10 @@ async fn write_role_config(home: &TempDir, name: &str, contents: &str) -> PathBu
|
||||
fn session_flags_layer_count(config: &Config) -> usize {
|
||||
config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| layer.name == ConfigLayerSource::SessionFlags)
|
||||
.count()
|
||||
@@ -51,7 +54,7 @@ async fn apply_role_defaults_to_default_and_leaves_config_unchanged() {
|
||||
let (_home, mut config) = test_config_with_cli_overrides(Vec::new()).await;
|
||||
let before = config.clone();
|
||||
|
||||
apply_role_to_config(&mut config, None)
|
||||
apply_role_to_config(&mut config, /*role_name*/ None)
|
||||
.await
|
||||
.expect("default role should apply");
|
||||
|
||||
@@ -529,7 +532,10 @@ writable_roots = ["./sandbox-root"]
|
||||
|
||||
let role_layer = config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.rfind(|layer| layer.name == ConfigLayerSource::SessionFlags)
|
||||
.expect("expected a session flags layer");
|
||||
@@ -630,7 +636,10 @@ enabled = false
|
||||
.expect("custom role should apply");
|
||||
|
||||
let plugins_manager = Arc::new(PluginsManager::new(home.path().to_path_buf()));
|
||||
let skills_manager = SkillsManager::new(home.path().to_path_buf(), true);
|
||||
let skills_manager = SkillsManager::new(
|
||||
home.path().to_path_buf(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
);
|
||||
let plugin_outcome = plugins_manager.plugins_for_config(&config);
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
|
||||
|
||||
@@ -76,7 +76,8 @@ mod tests {
|
||||
supports_websockets: false,
|
||||
};
|
||||
|
||||
let telemetry = collect_auth_env_telemetry(&provider, false);
|
||||
let telemetry =
|
||||
collect_auth_env_telemetry(&provider, /*codex_api_key_env_enabled*/ false);
|
||||
|
||||
assert_eq!(
|
||||
telemetry.provider_env_key_name,
|
||||
|
||||
@@ -52,7 +52,8 @@ fn serializes_text_schema_with_strict_format() {
|
||||
"required": ["answer"],
|
||||
});
|
||||
let text_controls =
|
||||
create_text_param_for_request(None, &Some(schema.clone())).expect("text controls");
|
||||
create_text_param_for_request(/*verbosity*/ None, &Some(schema.clone()))
|
||||
.expect("text controls");
|
||||
|
||||
let req = ResponsesApiRequest {
|
||||
model: "gpt-5.1".to_string(),
|
||||
|
||||
@@ -16,14 +16,14 @@ fn test_model_client(session_source: SessionSource) -> ModelClient {
|
||||
crate::model_provider_info::WireApi::Responses,
|
||||
);
|
||||
ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::new(),
|
||||
provider,
|
||||
session_source,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*model_verbosity*/ None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ fn test_session_telemetry() -> SessionTelemetry {
|
||||
ThreadId::new(),
|
||||
"gpt-test",
|
||||
"gpt-test",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
/*account_email*/ None,
|
||||
/*auth_mode*/ None,
|
||||
"test-originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test-terminal".to_string(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
@@ -91,7 +91,12 @@ async fn summarize_memories_returns_empty_for_empty_input() {
|
||||
let session_telemetry = test_session_telemetry();
|
||||
|
||||
let output = client
|
||||
.summarize_memories(Vec::new(), &model_info, None, &session_telemetry)
|
||||
.summarize_memories(
|
||||
Vec::new(),
|
||||
&model_info,
|
||||
/*effort*/ None,
|
||||
&session_telemetry,
|
||||
)
|
||||
.await
|
||||
.expect("empty summarize request should succeed");
|
||||
assert_eq!(output.len(), 0);
|
||||
|
||||
@@ -41,7 +41,7 @@ fn inter_agent_assistant_message(text: &str) -> ResponseItem {
|
||||
AgentPath::root().join("worker").unwrap(),
|
||||
Vec::new(),
|
||||
text.to_string(),
|
||||
true,
|
||||
/*trigger_turn*/ true,
|
||||
);
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
|
||||
@@ -247,17 +247,17 @@ async fn interrupting_regular_turn_waiting_on_startup_prewarm_emits_turn_aborted
|
||||
|
||||
fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
crate::client::ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::try_from("00000000-0000-4000-8000-000000000001")
|
||||
.expect("test thread id should be valid"),
|
||||
crate::model_provider_info::ModelProviderInfo::create_openai_provider(
|
||||
/* base_url */ None,
|
||||
/* base_url */ /*base_url*/ None,
|
||||
),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*model_verbosity*/ None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
)
|
||||
.new_session()
|
||||
}
|
||||
@@ -313,7 +313,7 @@ fn make_connector(id: &str, name: &str) -> AppInfo {
|
||||
|
||||
#[test]
|
||||
fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text() {
|
||||
let mut parsers = AssistantMessageStreamParsers::new(false);
|
||||
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false);
|
||||
let item_id = "msg-1";
|
||||
|
||||
let seeded = parsers.seed_item_text(item_id, "hello <oai-mem-citation>doc");
|
||||
@@ -330,7 +330,7 @@ fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text()
|
||||
|
||||
#[test]
|
||||
fn assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tail() {
|
||||
let mut parsers = AssistantMessageStreamParsers::new(false);
|
||||
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false);
|
||||
let item_id = "msg-1";
|
||||
|
||||
let seeded = parsers.seed_item_text(item_id, "hello <oai-mem-");
|
||||
@@ -347,7 +347,7 @@ fn assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tai
|
||||
|
||||
#[test]
|
||||
fn assistant_message_stream_parsers_seed_plan_parser_across_added_and_delta_boundaries() {
|
||||
let mut parsers = AssistantMessageStreamParsers::new(true);
|
||||
let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ true);
|
||||
let item_id = "msg-1";
|
||||
|
||||
let seeded = parsers.seed_item_text(item_id, "Intro\n<proposed");
|
||||
@@ -449,7 +449,7 @@ fn validated_network_policy_amendment_host_rejects_mismatch() {
|
||||
async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyhow::Result<()> {
|
||||
let spec = crate::config::NetworkProxySpec::from_config_and_constraints(
|
||||
NetworkProxyConfig::default(),
|
||||
None,
|
||||
/*requirements*/ None,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
)?;
|
||||
let mut exec_policy = Policy::empty();
|
||||
@@ -457,16 +457,16 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho
|
||||
"example.com",
|
||||
NetworkRuleProtocol::Https,
|
||||
Decision::Allow,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)?;
|
||||
|
||||
let (started_proxy, _) = Session::start_managed_network_proxy(
|
||||
&spec,
|
||||
&exec_policy,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
/*network_policy_decider*/ None,
|
||||
/*blocked_request_observer*/ None,
|
||||
/*managed_network_requirements_enabled*/ false,
|
||||
crate::config::NetworkProxyAuditMetadata::default(),
|
||||
)
|
||||
.await?;
|
||||
@@ -501,16 +501,16 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
|
||||
"example.com",
|
||||
NetworkRuleProtocol::Https,
|
||||
Decision::Allow,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)?;
|
||||
|
||||
let (started_proxy, _) = Session::start_managed_network_proxy(
|
||||
&spec,
|
||||
&exec_policy,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
/*network_policy_decider*/ None,
|
||||
/*blocked_request_observer*/ None,
|
||||
/*managed_network_requirements_enabled*/ false,
|
||||
crate::config::NetworkProxyAuditMetadata::default(),
|
||||
)
|
||||
.await?;
|
||||
@@ -737,7 +737,9 @@ fn non_app_mcp_tools_remain_visible_without_search_selection() {
|
||||
),
|
||||
(
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
make_mcp_tool("rmcp", "echo", None, None),
|
||||
make_mcp_tool(
|
||||
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -785,7 +787,9 @@ fn search_tool_selection_keeps_codex_apps_tools_without_mentions() {
|
||||
),
|
||||
(
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
make_mcp_tool("rmcp", "echo", None, None),
|
||||
make_mcp_tool(
|
||||
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -835,7 +839,9 @@ fn apps_mentions_add_codex_apps_tools_to_search_selected_set() {
|
||||
),
|
||||
(
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
make_mcp_tool("rmcp", "echo", None, None),
|
||||
make_mcp_tool(
|
||||
"rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -1179,7 +1185,13 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
codex_config::Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
let forked = initial
|
||||
.thread_manager
|
||||
.fork_thread(usize::MAX, fork_config, rollout_path, false, None)
|
||||
.fork_thread(
|
||||
usize::MAX,
|
||||
fork_config,
|
||||
rollout_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let collaboration_mode = CollaborationMode {
|
||||
@@ -1351,7 +1363,7 @@ async fn thread_rollback_drops_last_turn_from_history() {
|
||||
state.set_reference_context_item(Some(tc.to_turn_context_item()));
|
||||
}
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
|
||||
let rollback_event = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(rollback_event.num_turns, 1);
|
||||
@@ -1398,7 +1410,7 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns()
|
||||
.collect();
|
||||
sess.persist_rollout_items(&rollout_items).await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 99).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 99).await;
|
||||
|
||||
let rollback_event = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(rollback_event.num_turns, 99);
|
||||
@@ -1415,7 +1427,7 @@ async fn thread_rollback_fails_without_persisted_rollout_path() {
|
||||
sess.record_into_history(&initial_context, tc.as_ref())
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
|
||||
let error_event = wait_for_thread_rollback_failed(&rx).await;
|
||||
assert_eq!(
|
||||
@@ -1509,7 +1521,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context
|
||||
}))
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
let rollback_event = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(rollback_event.num_turns, 1);
|
||||
|
||||
@@ -1617,7 +1629,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio
|
||||
)
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
let rollback_event = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(rollback_event.num_turns, 1);
|
||||
|
||||
@@ -1695,10 +1707,10 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
|
||||
])
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
let first_rollback = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(first_rollback.num_turns, 1);
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
let second_rollback = wait_for_thread_rolled_back(&rx).await;
|
||||
assert_eq!(second_rollback.num_turns, 1);
|
||||
|
||||
@@ -1733,7 +1745,7 @@ async fn thread_rollback_fails_when_turn_in_progress() {
|
||||
.await;
|
||||
|
||||
*sess.active_turn.lock().await = Some(crate::state::ActiveTurn::default());
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await;
|
||||
|
||||
let error_event = wait_for_thread_rollback_failed(&rx).await;
|
||||
assert_eq!(
|
||||
@@ -1753,7 +1765,7 @@ async fn thread_rollback_fails_when_num_turns_is_zero() {
|
||||
sess.record_into_history(&initial_context, tc.as_ref())
|
||||
.await;
|
||||
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), 0).await;
|
||||
handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 0).await;
|
||||
|
||||
let error_event = wait_for_thread_rollback_failed(&rx).await;
|
||||
assert_eq!(error_event.message, "num_turns must be >= 1");
|
||||
@@ -2152,14 +2164,14 @@ async fn attach_rollout_recorder(session: &Arc<Session>) -> PathBuf {
|
||||
config.as_ref(),
|
||||
RolloutRecorderParams::new(
|
||||
ThreadId::default(),
|
||||
None,
|
||||
/*forked_from_id*/ None,
|
||||
SessionSource::Exec,
|
||||
BaseInstructions::default(),
|
||||
Vec::new(),
|
||||
EventPersistenceMode::Limited,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
/*state_db_ctx*/ None,
|
||||
/*state_builder*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("create rollout recorder");
|
||||
@@ -2198,11 +2210,11 @@ fn session_telemetry(
|
||||
conversation_id,
|
||||
ModelsManager::get_model_offline_for_tests(config.model.as_deref()).as_str(),
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(TelemetryAuthMode::Chatgpt),
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
session_source,
|
||||
)
|
||||
@@ -2325,7 +2337,7 @@ async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {
|
||||
.skills_manager
|
||||
.skills_for_cwd(
|
||||
&crate::skills_load_input_from_config(&parent_config, Vec::new()),
|
||||
true,
|
||||
/*force_reload*/ true,
|
||||
)
|
||||
.await;
|
||||
let parent_skill = parent_outcome
|
||||
@@ -2473,7 +2485,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
let models_manager = Arc::new(ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
@@ -2522,7 +2534,10 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
config.codex_home.clone(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
));
|
||||
let result = Session::new(
|
||||
session_configuration,
|
||||
Arc::clone(&config),
|
||||
@@ -2563,7 +2578,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let models_manager = Arc::new(ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let agent_control = AgentControl::default();
|
||||
@@ -2626,7 +2641,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let state = SessionState::new(session_configuration.clone());
|
||||
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
config.codex_home.clone(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
));
|
||||
let network_approval = Arc::new(NetworkApprovalService::default());
|
||||
let environment = Arc::new(
|
||||
codex_exec_server::Environment::create(/*exec_server_url*/ None)
|
||||
@@ -2712,7 +2730,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
per_turn_config,
|
||||
model_info,
|
||||
&models_manager,
|
||||
None,
|
||||
/*network*/ None,
|
||||
environment,
|
||||
"turn_id".to_string(),
|
||||
Arc::clone(&js_repl),
|
||||
@@ -3400,7 +3418,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
let models_manager = Arc::new(ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let agent_control = AgentControl::default();
|
||||
@@ -3463,7 +3481,10 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
let state = SessionState::new(session_configuration.clone());
|
||||
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
config.codex_home.clone(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
));
|
||||
let network_approval = Arc::new(NetworkApprovalService::default());
|
||||
let environment = Arc::new(
|
||||
codex_exec_server::Environment::create(/*exec_server_url*/ None)
|
||||
@@ -3549,7 +3570,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
per_turn_config,
|
||||
model_info,
|
||||
&models_manager,
|
||||
None,
|
||||
/*network*/ None,
|
||||
environment,
|
||||
"turn_id".to_string(),
|
||||
Arc::clone(&js_repl),
|
||||
@@ -3659,7 +3680,8 @@ async fn record_model_warning_appends_user_message() {
|
||||
#[tokio::test]
|
||||
async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() {
|
||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
||||
sess.set_previous_turn_settings(None).await;
|
||||
sess.set_previous_turn_settings(/*previous_turn_settings*/ None)
|
||||
.await;
|
||||
let input = vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
@@ -3712,7 +3734,10 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
|
||||
));
|
||||
let layers = config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
@@ -3909,7 +3934,7 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
|
||||
revised_prompt: Some("a tiny blue square".to_string()),
|
||||
result: "Zm9v".to_string(),
|
||||
}],
|
||||
None,
|
||||
/*reference_context_item*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -3963,7 +3988,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
|
||||
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
};
|
||||
handle_output_item_done(&mut ctx, item.clone(), None)
|
||||
handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None)
|
||||
.await
|
||||
.expect("image generation item should succeed");
|
||||
|
||||
@@ -4020,7 +4045,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
|
||||
tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
};
|
||||
handle_output_item_done(&mut ctx, item.clone(), None)
|
||||
handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None)
|
||||
.await
|
||||
.expect("image generation item should still complete");
|
||||
|
||||
@@ -4112,10 +4137,13 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
.await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.set_reference_context_item(/*item*/ None);
|
||||
}
|
||||
session
|
||||
.replace_history(vec![compacted_summary.clone()], None)
|
||||
.replace_history(
|
||||
vec![compacted_summary.clone()],
|
||||
/*reference_context_item*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
session
|
||||
@@ -4150,14 +4178,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
config.as_ref(),
|
||||
RolloutRecorderParams::new(
|
||||
ThreadId::default(),
|
||||
None,
|
||||
/*forked_from_id*/ None,
|
||||
SessionSource::Exec,
|
||||
BaseInstructions::default(),
|
||||
Vec::new(),
|
||||
EventPersistenceMode::Limited,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
/*state_db_ctx*/ None,
|
||||
/*state_builder*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("create rollout recorder");
|
||||
@@ -4247,14 +4275,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
|
||||
config.as_ref(),
|
||||
RolloutRecorderParams::new(
|
||||
ThreadId::default(),
|
||||
None,
|
||||
/*forked_from_id*/ None,
|
||||
SessionSource::Exec,
|
||||
BaseInstructions::default(),
|
||||
Vec::new(),
|
||||
EventPersistenceMode::Limited,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
/*state_db_ctx*/ None,
|
||||
/*state_builder*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("create rollout recorder");
|
||||
@@ -4276,7 +4304,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
|
||||
.await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.set_reference_context_item(/*item*/ None);
|
||||
}
|
||||
|
||||
session
|
||||
@@ -4315,7 +4343,7 @@ async fn run_user_shell_command_does_not_set_reference_context_item() {
|
||||
let (session, _turn_context, rx) = make_session_and_context_with_rx().await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(None);
|
||||
state.set_reference_context_item(/*item*/ None);
|
||||
}
|
||||
|
||||
handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string())
|
||||
@@ -4468,7 +4496,8 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input()
|
||||
.await
|
||||
.expect("inject pending input into active turn");
|
||||
|
||||
sess.on_task_finished(Arc::clone(&tc), None).await;
|
||||
sess.on_task_finished(Arc::clone(&tc), /*last_agent_message*/ None)
|
||||
.await;
|
||||
|
||||
let history = sess.clone_history().await;
|
||||
let expected = ResponseItem::Message {
|
||||
@@ -4560,7 +4589,7 @@ async fn steer_input_requires_active_turn() {
|
||||
}];
|
||||
|
||||
let err = sess
|
||||
.steer_input(input, None)
|
||||
.steer_input(input, /*expected_turn_id*/ None)
|
||||
.await
|
||||
.expect_err("steering without active turn should fail");
|
||||
|
||||
|
||||
@@ -425,11 +425,14 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
let models_manager = Arc::new(ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager.clone(),
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
|
||||
let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true));
|
||||
let skills_manager = Arc::new(SkillsManager::new(
|
||||
config.codex_home.clone(),
|
||||
/*bundled_skills_enabled*/ true,
|
||||
));
|
||||
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
|
||||
let skills_watcher = Arc::new(SkillsWatcher::noop());
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ fn blank_attribution_disables_trailer_prompt() {
|
||||
#[test]
|
||||
fn default_attribution_uses_codex_trailer() {
|
||||
assert_eq!(
|
||||
build_commit_message_trailer(None).as_deref(),
|
||||
build_commit_message_trailer(/*config_attribution*/ None).as_deref(),
|
||||
Some("Co-authored-by: Codex <noreply@openai.com>")
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ fn default_attribution_uses_codex_trailer() {
|
||||
#[test]
|
||||
fn resolve_value_handles_default_custom_and_blank() {
|
||||
assert_eq!(
|
||||
resolve_attribution_value(None),
|
||||
resolve_attribution_value(/*config_attribution*/ None),
|
||||
Some("Codex <noreply@openai.com>".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -216,8 +216,11 @@ async fn process_compacted_history_replaces_developer_messages() {
|
||||
phase: None,
|
||||
},
|
||||
];
|
||||
let (refreshed, mut expected) =
|
||||
process_compacted_history_with_test_session(compacted_history, None).await;
|
||||
let (refreshed, mut expected) = process_compacted_history_with_test_session(
|
||||
compacted_history,
|
||||
/*previous_turn_settings*/ None,
|
||||
)
|
||||
.await;
|
||||
expected.push(ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
@@ -241,8 +244,11 @@ async fn process_compacted_history_reinjects_full_initial_context() {
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
}];
|
||||
let (refreshed, mut expected) =
|
||||
process_compacted_history_with_test_session(compacted_history, None).await;
|
||||
let (refreshed, mut expected) = process_compacted_history_with_test_session(
|
||||
compacted_history,
|
||||
/*previous_turn_settings*/ None,
|
||||
)
|
||||
.await;
|
||||
expected.push(ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
@@ -317,8 +323,11 @@ keep me updated
|
||||
phase: None,
|
||||
},
|
||||
];
|
||||
let (refreshed, mut expected) =
|
||||
process_compacted_history_with_test_session(compacted_history, None).await;
|
||||
let (refreshed, mut expected) = process_compacted_history_with_test_session(
|
||||
compacted_history,
|
||||
/*previous_turn_settings*/ None,
|
||||
)
|
||||
.await;
|
||||
expected.push(ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
@@ -363,8 +372,11 @@ async fn process_compacted_history_inserts_context_before_last_real_user_message
|
||||
},
|
||||
];
|
||||
|
||||
let (refreshed, initial_context) =
|
||||
process_compacted_history_with_test_session(compacted_history, None).await;
|
||||
let (refreshed, initial_context) = process_compacted_history_with_test_session(
|
||||
compacted_history,
|
||||
/*previous_turn_settings*/ None,
|
||||
)
|
||||
.await;
|
||||
let mut expected = vec![
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
|
||||
@@ -507,7 +507,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(None),
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -709,7 +709,10 @@ fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> {
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::unknown(":future_special_path", None),
|
||||
value: FileSystemSpecialPath::unknown(
|
||||
":future_special_path",
|
||||
/*subpath*/ None
|
||||
),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]),
|
||||
@@ -976,10 +979,10 @@ network_access = false # This should be ignored.
|
||||
let sandbox_mode_override = None;
|
||||
let resolution = sandbox_full_access_cfg.derive_sandbox_policy(
|
||||
sandbox_mode_override,
|
||||
None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
/*sandbox_policy_constraint*/ None,
|
||||
);
|
||||
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
|
||||
|
||||
@@ -995,10 +998,10 @@ network_access = true # This should be ignored.
|
||||
let sandbox_mode_override = None;
|
||||
let resolution = sandbox_read_only_cfg.derive_sandbox_policy(
|
||||
sandbox_mode_override,
|
||||
None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
/*sandbox_policy_constraint*/ None,
|
||||
);
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
|
||||
@@ -1022,10 +1025,10 @@ exclude_slash_tmp = true
|
||||
let sandbox_mode_override = None;
|
||||
let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy(
|
||||
sandbox_mode_override,
|
||||
None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
/*sandbox_policy_constraint*/ None,
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
@@ -1064,10 +1067,10 @@ trust_level = "trusted"
|
||||
let sandbox_mode_override = None;
|
||||
let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy(
|
||||
sandbox_mode_override,
|
||||
None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
/*sandbox_policy_constraint*/ None,
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
@@ -1245,7 +1248,7 @@ fn filter_mcp_servers_by_allowlist_allows_all_when_unset() {
|
||||
("server-b".to_string(), http_mcp("https://example.com/b")),
|
||||
]);
|
||||
|
||||
filter_mcp_servers_by_requirements(&mut servers, None);
|
||||
filter_mcp_servers_by_requirements(&mut servers, /*mcp_requirements*/ None);
|
||||
|
||||
assert_eq!(
|
||||
servers
|
||||
@@ -1874,7 +1877,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -1904,7 +1907,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
||||
let empty = BTreeMap::new();
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(empty.clone())],
|
||||
)?;
|
||||
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
||||
@@ -2101,7 +2104,7 @@ async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2174,7 +2177,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2227,7 +2230,7 @@ async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2278,7 +2281,7 @@ async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2344,7 +2347,7 @@ async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyh
|
||||
)]);
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2424,7 +2427,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
let serialized_with_optional = std::fs::read_to_string(&config_path)?;
|
||||
@@ -2455,7 +2458,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
);
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2546,7 +2549,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2630,7 +2633,7 @@ async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2677,7 +2680,7 @@ async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2724,7 +2727,7 @@ async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2775,7 +2778,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
@@ -2929,7 +2932,7 @@ async fn set_feature_enabled_updates_profile() -> anyhow::Result<()> {
|
||||
|
||||
ConfigEditsBuilder::new(codex_home.path())
|
||||
.with_profile(Some("dev"))
|
||||
.set_feature_enabled("guardian_approval", true)
|
||||
.set_feature_enabled("guardian_approval", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
@@ -2965,13 +2968,13 @@ async fn set_feature_enabled_persists_default_false_feature_disable_in_profile()
|
||||
|
||||
ConfigEditsBuilder::new(codex_home.path())
|
||||
.with_profile(Some("dev"))
|
||||
.set_feature_enabled("guardian_approval", true)
|
||||
.set_feature_enabled("guardian_approval", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
ConfigEditsBuilder::new(codex_home.path())
|
||||
.with_profile(Some("dev"))
|
||||
.set_feature_enabled("guardian_approval", false)
|
||||
.set_feature_enabled("guardian_approval", /*enabled*/ false)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
@@ -3005,13 +3008,13 @@ async fn set_feature_enabled_profile_disable_overrides_root_enable() -> anyhow::
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
ConfigEditsBuilder::new(codex_home.path())
|
||||
.set_feature_enabled("guardian_approval", true)
|
||||
.set_feature_enabled("guardian_approval", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
ConfigEditsBuilder::new(codex_home.path())
|
||||
.with_profile(Some("dev"))
|
||||
.set_feature_enabled("guardian_approval", false)
|
||||
.set_feature_enabled("guardian_approval", /*enabled*/ false)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
@@ -4323,7 +4326,8 @@ model_verbosity = "high"
|
||||
supports_websockets: false,
|
||||
};
|
||||
let model_provider_map = {
|
||||
let mut model_provider_map = built_in_model_providers(/* openai_base_url */ None);
|
||||
let mut model_provider_map =
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None);
|
||||
model_provider_map.insert("openai-custom".to_string(), openai_custom_provider.clone());
|
||||
model_provider_map
|
||||
};
|
||||
@@ -4392,7 +4396,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
windows_sandbox_private_desktop: true,
|
||||
},
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4534,7 +4538,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
windows_sandbox_private_desktop: true,
|
||||
},
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4674,7 +4678,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
windows_sandbox_private_desktop: true,
|
||||
},
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4800,7 +4804,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
windows_sandbox_private_desktop: true,
|
||||
},
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -5142,11 +5146,11 @@ trust_level = "untrusted"
|
||||
.expect("TOML deserialization should succeed");
|
||||
|
||||
let resolution = cfg.derive_sandbox_policy(
|
||||
None,
|
||||
None,
|
||||
/*sandbox_mode_override*/ None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
/*sandbox_policy_constraint*/ None,
|
||||
);
|
||||
|
||||
// Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)
|
||||
@@ -5194,8 +5198,8 @@ fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defaults()
|
||||
})?;
|
||||
|
||||
let resolution = cfg.derive_sandbox_policy(
|
||||
None,
|
||||
None,
|
||||
/*sandbox_mode_override*/ None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&project_path,
|
||||
Some(&constrained),
|
||||
@@ -5234,8 +5238,8 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback()
|
||||
})?;
|
||||
|
||||
let resolution = cfg.derive_sandbox_policy(
|
||||
None,
|
||||
None,
|
||||
/*sandbox_mode_override*/ None,
|
||||
/*profile_sandbox_mode*/ None,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
&project_path,
|
||||
Some(&constrained),
|
||||
@@ -5252,7 +5256,11 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback()
|
||||
#[test]
|
||||
fn test_resolve_oss_provider_explicit_override() {
|
||||
let config_toml = ConfigToml::default();
|
||||
let result = resolve_oss_provider(Some("custom-provider"), &config_toml, None);
|
||||
let result = resolve_oss_provider(
|
||||
Some("custom-provider"),
|
||||
&config_toml,
|
||||
/*config_profile*/ None,
|
||||
);
|
||||
assert_eq!(result, Some("custom-provider".to_string()));
|
||||
}
|
||||
|
||||
@@ -5269,7 +5277,11 @@ fn test_resolve_oss_provider_from_profile() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string()));
|
||||
let result = resolve_oss_provider(
|
||||
/*explicit_provider*/ None,
|
||||
&config_toml,
|
||||
Some("test-profile".to_string()),
|
||||
);
|
||||
assert_eq!(result, Some("profile-provider".to_string()));
|
||||
}
|
||||
|
||||
@@ -5280,7 +5292,11 @@ fn test_resolve_oss_provider_from_global_config() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = resolve_oss_provider(None, &config_toml, None);
|
||||
let result = resolve_oss_provider(
|
||||
/*explicit_provider*/ None,
|
||||
&config_toml,
|
||||
/*config_profile*/ None,
|
||||
);
|
||||
assert_eq!(result, Some("global-provider".to_string()));
|
||||
}
|
||||
|
||||
@@ -5295,14 +5311,22 @@ fn test_resolve_oss_provider_profile_fallback_to_global() {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string()));
|
||||
let result = resolve_oss_provider(
|
||||
/*explicit_provider*/ None,
|
||||
&config_toml,
|
||||
Some("test-profile".to_string()),
|
||||
);
|
||||
assert_eq!(result, Some("global-provider".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_oss_provider_none_when_not_configured() {
|
||||
let config_toml = ConfigToml::default();
|
||||
let result = resolve_oss_provider(None, &config_toml, None);
|
||||
let result = resolve_oss_provider(
|
||||
/*explicit_provider*/ None,
|
||||
&config_toml,
|
||||
/*config_profile*/ None,
|
||||
);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ fn blocking_set_model_top_level() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: Some("gpt-5.1-codex".to_string()),
|
||||
effort: Some(ReasoningEffort::High),
|
||||
@@ -150,7 +150,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } }
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: Some("o4-mini".to_string()),
|
||||
effort: None,
|
||||
@@ -195,7 +195,7 @@ fn blocking_set_model_writes_through_symlink_chain() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: Some("gpt-5.1-codex".to_string()),
|
||||
effort: Some(ReasoningEffort::High),
|
||||
@@ -228,7 +228,7 @@ fn blocking_set_model_replaces_symlink_on_cycle() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: Some("gpt-5.1-codex".to_string()),
|
||||
effort: None,
|
||||
@@ -267,7 +267,7 @@ network_access = false
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[
|
||||
ConfigEdit::SetPath {
|
||||
segments: vec![
|
||||
@@ -322,7 +322,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } }
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: None,
|
||||
effort: Some(ReasoningEffort::High),
|
||||
@@ -356,7 +356,7 @@ model_reasoning_effort = "low"
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetModel {
|
||||
model: Some("o5-preview".to_string()),
|
||||
effort: Some(ReasoningEffort::Minimal),
|
||||
@@ -420,7 +420,7 @@ existing = "value"
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeHideFullAccessWarning(true)],
|
||||
)
|
||||
.expect("persist");
|
||||
@@ -450,7 +450,7 @@ existing = "value"
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeHideRateLimitModelNudge(true)],
|
||||
)
|
||||
.expect("persist");
|
||||
@@ -476,7 +476,7 @@ existing = "value"
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeHideModelMigrationPrompt(
|
||||
"hide_gpt5_1_migration_prompt".to_string(),
|
||||
true,
|
||||
@@ -505,7 +505,7 @@ existing = "value"
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetNoticeHideModelMigrationPrompt(
|
||||
"hide_gpt-5.1-codex-max_migration_prompt".to_string(),
|
||||
true,
|
||||
@@ -534,7 +534,7 @@ existing = "value"
|
||||
.expect("seed");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::RecordModelMigrationSeen {
|
||||
from: "gpt-5".to_string(),
|
||||
to: "gpt-5.1".to_string(),
|
||||
@@ -616,7 +616,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)
|
||||
.expect("persist");
|
||||
@@ -681,7 +681,12 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() {
|
||||
},
|
||||
);
|
||||
|
||||
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let raw = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = "\
|
||||
@@ -731,7 +736,12 @@ foo = { command = "cmd" }
|
||||
},
|
||||
);
|
||||
|
||||
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[mcp_servers]
|
||||
@@ -777,7 +787,12 @@ foo = { command = "cmd" } # keep me
|
||||
},
|
||||
);
|
||||
|
||||
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[mcp_servers]
|
||||
@@ -822,7 +837,12 @@ foo = { command = "cmd", args = ["--flag"] } # keep me
|
||||
},
|
||||
);
|
||||
|
||||
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[mcp_servers]
|
||||
@@ -868,7 +888,12 @@ foo = { command = "cmd" }
|
||||
},
|
||||
);
|
||||
|
||||
apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist");
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers)],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config");
|
||||
let expected = r#"[mcp_servers]
|
||||
@@ -885,7 +910,7 @@ fn blocking_clear_path_noop_when_missing() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ClearPath {
|
||||
segments: vec!["missing".to_string()],
|
||||
}],
|
||||
@@ -906,7 +931,7 @@ fn blocking_set_path_updates_notifications() {
|
||||
let item = value(false);
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::SetPath {
|
||||
segments: vec!["tui".to_string(), "notifications".to_string()],
|
||||
value: item,
|
||||
@@ -982,7 +1007,7 @@ async fn blocking_set_asynchronous_helpers_available() {
|
||||
let codex_home = tmp.path().to_path_buf();
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_hide_full_access_warning(true)
|
||||
.set_hide_full_access_warning(/*acknowledged*/ true)
|
||||
.apply()
|
||||
.await
|
||||
.expect("persist");
|
||||
@@ -1024,7 +1049,7 @@ fn blocking_builder_set_realtime_audio_persists_and_clears() {
|
||||
);
|
||||
|
||||
ConfigEditsBuilder::new(codex_home)
|
||||
.set_realtime_microphone(None)
|
||||
.set_realtime_microphone(/*microphone*/ None)
|
||||
.apply_blocking()
|
||||
.expect("clear realtime microphone");
|
||||
|
||||
@@ -1053,7 +1078,7 @@ fn replace_mcp_servers_blocking_clears_table_when_empty() {
|
||||
|
||||
apply_blocking(
|
||||
codex_home,
|
||||
None,
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(BTreeMap::new())],
|
||||
)
|
||||
.expect("persist");
|
||||
|
||||
@@ -96,7 +96,10 @@ impl ManagedFeatures {
|
||||
impl From<Features> for ManagedFeatures {
|
||||
fn from(features: Features) -> Self {
|
||||
Self {
|
||||
value: ConstrainedWithSource::new(Constrained::allow_any(features), None),
|
||||
value: ConstrainedWithSource::new(
|
||||
Constrained::allow_any(features),
|
||||
/*source*/ None,
|
||||
),
|
||||
pinned_features: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() {
|
||||
let parsed =
|
||||
normalize_absolute_path_for_platform(r"\\?\D:\c\x\worktrees\2508\swift-base", true);
|
||||
let parsed = normalize_absolute_path_for_platform(
|
||||
r"\\?\D:\c\x\worktrees\2508\swift-base",
|
||||
/*is_windows*/ true,
|
||||
);
|
||||
assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base"));
|
||||
}
|
||||
|
||||
|
||||
@@ -797,7 +797,7 @@ async fn load_config_layers_fails_when_cloud_requirements_loader_fails() -> anyh
|
||||
CloudRequirementsLoader::new(async {
|
||||
Err(CloudRequirementsLoadError::new(
|
||||
codex_config::CloudRequirementsLoadErrorCode::RequestFailed,
|
||||
None,
|
||||
/*status_code*/ None,
|
||||
"cloud requirements failed",
|
||||
))
|
||||
}),
|
||||
@@ -833,7 +833,13 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> {
|
||||
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
|
||||
let layers = load_config_layers_state(
|
||||
&codex_home,
|
||||
@@ -899,7 +905,13 @@ model_instructions_file = "child.txt"
|
||||
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home)
|
||||
@@ -965,7 +977,13 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s
|
||||
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
|
||||
let layers = load_config_layers_state(
|
||||
&codex_home,
|
||||
@@ -1018,7 +1036,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R
|
||||
let project_layers: Vec<_> = layers
|
||||
.get_layers(
|
||||
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
true,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
|
||||
@@ -1046,7 +1064,13 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul
|
||||
tokio::fs::write(nested_dot_codex.join(CONFIG_TOML_FILE), "foo = \"child\"\n").await?;
|
||||
|
||||
tokio::fs::create_dir_all(&project_dot_codex).await?;
|
||||
make_config_for_test(&project_dot_codex, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&project_dot_codex,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let user_config_path = project_dot_codex.join(CONFIG_TOML_FILE);
|
||||
let user_config_contents = tokio::fs::read_to_string(&user_config_path).await?;
|
||||
tokio::fs::write(
|
||||
@@ -1068,7 +1092,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul
|
||||
let project_layers: Vec<_> = layers
|
||||
.get_layers(
|
||||
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
true,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
|
||||
@@ -1115,7 +1139,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
|
||||
&codex_home_untrusted,
|
||||
&project_root,
|
||||
TrustLevel::Untrusted,
|
||||
None,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let untrusted_config_path = codex_home_untrusted.join(CONFIG_TOML_FILE);
|
||||
@@ -1137,7 +1161,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
|
||||
let project_layers_untrusted: Vec<_> = layers_untrusted
|
||||
.get_layers(
|
||||
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
true,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
|
||||
@@ -1175,7 +1199,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result<
|
||||
let project_layers_unknown: Vec<_> = layers_unknown
|
||||
.get_layers(
|
||||
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
true,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
|
||||
@@ -1218,7 +1242,13 @@ enabled = false
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home)
|
||||
@@ -1303,7 +1333,13 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io::
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
|
||||
if let Some(trust_level) = trust_level {
|
||||
make_config_for_test(&codex_home, &project_root, trust_level, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
trust_level,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let config_contents = tokio::fs::read_to_string(&config_path).await?;
|
||||
tokio::fs::write(&config_path, format!("foo = \"user\"\n{config_contents}")).await?;
|
||||
} else {
|
||||
@@ -1321,7 +1357,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io::
|
||||
let project_layers: Vec<_> = layers
|
||||
.get_layers(
|
||||
super::ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
true,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. }))
|
||||
@@ -1358,7 +1394,13 @@ async fn cli_overrides_with_relative_paths_do_not_break_trust_check() -> std::io
|
||||
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
|
||||
let cli_overrides = vec![(
|
||||
|
||||
@@ -171,7 +171,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
|
||||
codex_app_tool(
|
||||
"calendar_list_events",
|
||||
"calendar",
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
&["sample", "sample"],
|
||||
),
|
||||
),
|
||||
@@ -229,8 +229,8 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
|
||||
.build()
|
||||
.await
|
||||
.expect("config should load");
|
||||
let _ = config.features.set_enabled(Feature::Apps, true);
|
||||
let cache_key = accessible_connectors_cache_key(&config, None);
|
||||
let _ = config.features.set_enabled(Feature::Apps, /*enabled*/ true);
|
||||
let cache_key = accessible_connectors_cache_key(&config, /*auth*/ None);
|
||||
let tools = HashMap::from([
|
||||
(
|
||||
"mcp__codex_apps__calendar_list_events".to_string(),
|
||||
@@ -253,7 +253,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
|
||||
]);
|
||||
|
||||
let cached = with_accessible_connectors_cache_cleared(|| {
|
||||
refresh_accessible_connectors_cache_from_mcp_tools(&config, None, &tools);
|
||||
refresh_accessible_connectors_cache_from_mcp_tools(&config, /*auth*/ None, &tools);
|
||||
read_cached_accessible_connectors(&cache_key).expect("cache should be populated")
|
||||
});
|
||||
|
||||
@@ -367,8 +367,8 @@ fn app_tool_policy_uses_global_defaults_for_destructive_hints() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/create",
|
||||
None,
|
||||
Some(&annotations(Some(true), None)),
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(Some(true), /*open_world_hint*/ None)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -392,7 +392,7 @@ fn app_is_enabled_uses_default_for_unconfigured_apps() {
|
||||
};
|
||||
|
||||
assert!(!app_is_enabled(&apps_config, Some("calendar")));
|
||||
assert!(!app_is_enabled(&apps_config, None));
|
||||
assert!(!app_is_enabled(&apps_config, /*connector_id*/ None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -518,7 +518,13 @@ enabled = true
|
||||
.await
|
||||
.expect("config should build");
|
||||
|
||||
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
|
||||
let policy = app_tool_policy(
|
||||
&config,
|
||||
Some("connector_123123"),
|
||||
"events.list",
|
||||
/*tool_title*/ None,
|
||||
/*annotations*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
policy,
|
||||
AppToolPolicy {
|
||||
@@ -555,7 +561,13 @@ async fn cloud_requirements_disable_connector_applies_without_user_apps_table()
|
||||
.await
|
||||
.expect("config should build");
|
||||
|
||||
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
|
||||
let policy = app_tool_policy(
|
||||
&config,
|
||||
Some("connector_123123"),
|
||||
"events.list",
|
||||
/*tool_title*/ None,
|
||||
/*annotations*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
policy,
|
||||
AppToolPolicy {
|
||||
@@ -602,7 +614,13 @@ enabled = true
|
||||
.expect("apps config"),
|
||||
);
|
||||
|
||||
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
|
||||
let policy = app_tool_policy(
|
||||
&config,
|
||||
Some("connector_123123"),
|
||||
"events.list",
|
||||
/*tool_title*/ None,
|
||||
/*annotations*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
policy,
|
||||
AppToolPolicy {
|
||||
@@ -637,7 +655,13 @@ async fn local_requirements_disable_connector_applies_without_user_apps_table()
|
||||
ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements)
|
||||
.expect("requirements stack");
|
||||
|
||||
let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None);
|
||||
let policy = app_tool_policy(
|
||||
&config,
|
||||
Some("connector_123123"),
|
||||
"events.list",
|
||||
/*tool_title*/ None,
|
||||
/*annotations*/ None,
|
||||
);
|
||||
assert_eq!(
|
||||
policy,
|
||||
AppToolPolicy {
|
||||
@@ -699,8 +723,10 @@ fn app_tool_policy_honors_default_app_enabled_false() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/list",
|
||||
None,
|
||||
Some(&annotations(None, None)),
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(
|
||||
/*destructive_hint*/ None, /*open_world_hint*/ None,
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -737,8 +763,10 @@ fn app_tool_policy_allows_per_app_enable_when_default_is_disabled() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/list",
|
||||
None,
|
||||
Some(&annotations(None, None)),
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(
|
||||
/*destructive_hint*/ None, /*open_world_hint*/ None,
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -779,7 +807,7 @@ fn app_tool_policy_per_tool_enabled_true_overrides_app_level_disable_flags() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/create",
|
||||
None,
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(Some(true), Some(true))),
|
||||
);
|
||||
|
||||
@@ -813,7 +841,7 @@ fn app_tool_policy_default_tools_enabled_true_overrides_app_level_tool_hints() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/create",
|
||||
None,
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(Some(true), Some(true))),
|
||||
);
|
||||
|
||||
@@ -847,8 +875,10 @@ fn app_tool_policy_default_tools_enabled_false_overrides_app_level_tool_hints()
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/list",
|
||||
None,
|
||||
Some(&annotations(None, None)),
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(
|
||||
/*destructive_hint*/ None, /*open_world_hint*/ None,
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -883,8 +913,10 @@ fn app_tool_policy_uses_default_tools_approval_mode() {
|
||||
Some(&apps_config),
|
||||
Some("calendar"),
|
||||
"events/list",
|
||||
None,
|
||||
Some(&annotations(None, None)),
|
||||
/*tool_title*/ None,
|
||||
Some(&annotations(
|
||||
/*destructive_hint*/ None, /*open_world_hint*/ None,
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -51,7 +51,7 @@ fn inter_agent_assistant_msg(text: &str) -> ResponseItem {
|
||||
AgentPath::root().join("worker").unwrap(),
|
||||
Vec::new(),
|
||||
text.to_string(),
|
||||
true,
|
||||
/*trigger_turn*/ true,
|
||||
);
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
@@ -246,7 +246,8 @@ fn filters_non_api_messages() {
|
||||
|
||||
#[test]
|
||||
fn non_last_reasoning_tokens_return_zero_when_no_user_messages() {
|
||||
let history = create_history_with_items(vec![reasoning_with_encrypted_content(800)]);
|
||||
let history =
|
||||
create_history_with_items(vec![reasoning_with_encrypted_content(/*len*/ 800)]);
|
||||
|
||||
assert_eq!(history.get_non_last_reasoning_items_tokens(), 0);
|
||||
}
|
||||
@@ -254,11 +255,11 @@ fn non_last_reasoning_tokens_return_zero_when_no_user_messages() {
|
||||
#[test]
|
||||
fn non_last_reasoning_tokens_ignore_entries_after_last_user() {
|
||||
let history = create_history_with_items(vec![
|
||||
reasoning_with_encrypted_content(900),
|
||||
reasoning_with_encrypted_content(/*len*/ 900),
|
||||
user_msg("first"),
|
||||
reasoning_with_encrypted_content(1_000),
|
||||
reasoning_with_encrypted_content(/*len*/ 1_000),
|
||||
user_msg("second"),
|
||||
reasoning_with_encrypted_content(2_000),
|
||||
reasoning_with_encrypted_content(/*len*/ 2_000),
|
||||
]);
|
||||
// first: (900 * 0.75 - 650) / 4 = 6.25 tokens
|
||||
// second: (1000 * 0.75 - 650) / 4 = 25 tokens
|
||||
@@ -330,7 +331,7 @@ fn drop_last_n_user_turns_treats_inter_agent_assistant_messages_as_instruction_t
|
||||
inter_agent_reply,
|
||||
]);
|
||||
|
||||
history.drop_last_n_user_turns(1);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 1);
|
||||
|
||||
assert_eq!(history.raw_items(), &vec![first_turn, first_reply]);
|
||||
}
|
||||
@@ -352,7 +353,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
|
||||
total_tokens: 100,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
/*model_context_window*/ None,
|
||||
);
|
||||
let added_user = user_msg("new user message");
|
||||
let added_tool_output = custom_tool_call_output("tool-tail", "new tool output");
|
||||
@@ -362,7 +363,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
history.get_total_token_usage(true),
|
||||
history.get_total_token_usage(/*server_reasoning_included*/ true),
|
||||
100 + estimate_item_token_count(&added_user)
|
||||
+ estimate_item_token_count(&added_tool_output)
|
||||
);
|
||||
@@ -606,7 +607,12 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() {
|
||||
#[test]
|
||||
fn get_history_for_prompt_drops_ghost_commits() {
|
||||
let items = vec![ResponseItem::GhostSnapshot {
|
||||
ghost_commit: GhostCommit::new("ghost-1".to_string(), None, Vec::new(), Vec::new()),
|
||||
ghost_commit: GhostCommit::new(
|
||||
"ghost-1".to_string(),
|
||||
/*parent*/ None,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
}];
|
||||
let history = create_history_with_items(items);
|
||||
let modalities = default_input_modalities();
|
||||
@@ -792,7 +798,7 @@ fn drop_last_n_user_turns_preserves_prefix() {
|
||||
|
||||
let modalities = default_input_modalities();
|
||||
let mut history = create_history_with_items(items);
|
||||
history.drop_last_n_user_turns(1);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 1);
|
||||
assert_eq!(
|
||||
history.for_prompt(&modalities),
|
||||
vec![
|
||||
@@ -809,7 +815,7 @@ fn drop_last_n_user_turns_preserves_prefix() {
|
||||
user_msg("u2"),
|
||||
assistant_msg("a2"),
|
||||
]);
|
||||
history.drop_last_n_user_turns(99);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 99);
|
||||
assert_eq!(
|
||||
history.for_prompt(&modalities),
|
||||
vec![assistant_msg("session prefix item")]
|
||||
@@ -838,7 +844,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
|
||||
|
||||
let modalities = default_input_modalities();
|
||||
let mut history = create_history_with_items(items);
|
||||
history.drop_last_n_user_turns(1);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 1);
|
||||
|
||||
let expected_prefix_and_first_turn = vec![
|
||||
user_input_text_msg("<environment_context>ctx</environment_context>"),
|
||||
@@ -892,7 +898,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
|
||||
user_input_text_msg("turn 2 user"),
|
||||
assistant_msg("turn 2 assistant"),
|
||||
]);
|
||||
history.drop_last_n_user_turns(2);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 2);
|
||||
assert_eq!(history.for_prompt(&modalities), expected_prefix_only);
|
||||
|
||||
let mut history = create_history_with_items(vec![
|
||||
@@ -912,7 +918,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() {
|
||||
user_input_text_msg("turn 2 user"),
|
||||
assistant_msg("turn 2 assistant"),
|
||||
]);
|
||||
history.drop_last_n_user_turns(3);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 3);
|
||||
assert_eq!(history.for_prompt(&modalities), expected_prefix_only);
|
||||
}
|
||||
|
||||
@@ -935,7 +941,7 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {
|
||||
let mut history = create_history_with_items(items);
|
||||
let reference_context_item = reference_context_item();
|
||||
history.set_reference_context_item(Some(reference_context_item.clone()));
|
||||
history.drop_last_n_user_turns(1);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 1);
|
||||
|
||||
assert_eq!(
|
||||
history.clone().for_prompt(&modalities),
|
||||
@@ -973,7 +979,7 @@ fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_b
|
||||
let modalities = default_input_modalities();
|
||||
let mut history = create_history_with_items(items);
|
||||
history.set_reference_context_item(Some(reference_context_item()));
|
||||
history.drop_last_n_user_turns(1);
|
||||
history.drop_last_n_user_turns(/*num_turns*/ 1);
|
||||
|
||||
assert_eq!(
|
||||
history.clone().for_prompt(&modalities),
|
||||
@@ -1165,7 +1171,7 @@ fn format_exec_output_truncates_large_error() {
|
||||
|
||||
let truncated = truncate_exec_output(&large_error);
|
||||
|
||||
assert_truncated_message_matches(&truncated, line, 36250);
|
||||
assert_truncated_message_matches(&truncated, line, /*expected_removed*/ 36250);
|
||||
assert_ne!(truncated, large_error);
|
||||
}
|
||||
|
||||
@@ -1174,7 +1180,7 @@ fn format_exec_output_marks_byte_truncation_without_omitted_lines() {
|
||||
let long_line = "a".repeat(EXEC_FORMAT_MAX_BYTES + 10000);
|
||||
let truncated = truncate_exec_output(&long_line);
|
||||
assert_ne!(truncated, long_line);
|
||||
assert_truncated_message_matches(&truncated, "a", 2500);
|
||||
assert_truncated_message_matches(&truncated, "a", /*expected_removed*/ 2500);
|
||||
assert!(
|
||||
!truncated.contains("omitted"),
|
||||
"line omission marker should not appear when no lines were dropped: {truncated}"
|
||||
@@ -1196,7 +1202,7 @@ fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail() {
|
||||
.collect();
|
||||
|
||||
let truncated = truncate_exec_output(&content);
|
||||
assert_truncated_message_matches(&truncated, "line-0-", 34_723);
|
||||
assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 34_723);
|
||||
assert!(
|
||||
truncated.contains("line-0-"),
|
||||
"expected head line to remain: {truncated}"
|
||||
@@ -1219,7 +1225,7 @@ fn format_exec_output_prefers_line_marker_when_both_limits_exceeded() {
|
||||
|
||||
let truncated = truncate_exec_output(&content);
|
||||
|
||||
assert_truncated_message_matches(&truncated, "line-0-", 17_423);
|
||||
assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 17_423);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
||||
@@ -86,8 +86,8 @@ fn detects_hook_prompt_fragment_and_roundtrips_escaping() {
|
||||
let ContentItem::InputText { text } = content_item else {
|
||||
panic!("expected input text content item");
|
||||
};
|
||||
let parsed =
|
||||
parse_visible_hook_prompt_message(None, content.as_slice()).expect("visible hook prompt");
|
||||
let parsed = parse_visible_hook_prompt_message(/*id*/ None, content.as_slice())
|
||||
.expect("visible hook prompt");
|
||||
assert_eq!(
|
||||
parsed.fragments,
|
||||
vec![HookPromptFragment {
|
||||
|
||||
@@ -20,8 +20,8 @@ fn serialize_workspace_write_environment_context() {
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = format!(
|
||||
@@ -49,7 +49,7 @@ fn serialize_environment_context_with_network() {
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
Some(network),
|
||||
None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = format!(
|
||||
@@ -73,12 +73,12 @@ fn serialize_environment_context_with_network() {
|
||||
#[test]
|
||||
fn serialize_read_only_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
None,
|
||||
/*cwd*/ None,
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -93,12 +93,12 @@ fn serialize_read_only_environment_context() {
|
||||
#[test]
|
||||
fn serialize_external_sandbox_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
None,
|
||||
/*cwd*/ None,
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -113,12 +113,12 @@ fn serialize_external_sandbox_environment_context() {
|
||||
#[test]
|
||||
fn serialize_external_sandbox_with_restricted_network_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
None,
|
||||
/*cwd*/ None,
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -133,12 +133,12 @@ fn serialize_external_sandbox_with_restricted_network_environment_context() {
|
||||
#[test]
|
||||
fn serialize_full_access_environment_context() {
|
||||
let context = EnvironmentContext::new(
|
||||
None,
|
||||
/*cwd*/ None,
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
let expected = r#"<environment_context>
|
||||
@@ -155,18 +155,18 @@ fn equals_except_shell_compares_cwd() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
assert!(context1.equals_except_shell(&context2));
|
||||
}
|
||||
@@ -176,18 +176,18 @@ fn equals_except_shell_ignores_sandbox_policy() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
assert!(context1.equals_except_shell(&context2));
|
||||
@@ -198,18 +198,18 @@ fn equals_except_shell_compares_cwd_differences() {
|
||||
let context1 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo1")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo2")),
|
||||
fake_shell(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
assert!(!context1.equals_except_shell(&context2));
|
||||
@@ -224,10 +224,10 @@ fn equals_except_shell_ignores_shell() {
|
||||
shell_path: "/bin/bash".into(),
|
||||
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
let context2 = EnvironmentContext::new(
|
||||
Some(PathBuf::from("/repo")),
|
||||
@@ -236,10 +236,10 @@ fn equals_except_shell_ignores_shell() {
|
||||
shell_path: "/bin/zsh".into(),
|
||||
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*current_date*/ None,
|
||||
/*timezone*/ None,
|
||||
/*network*/ None,
|
||||
/*subagents*/ None,
|
||||
);
|
||||
|
||||
assert!(context1.equals_except_shell(&context2));
|
||||
@@ -252,7 +252,7 @@ fn serialize_environment_context_with_subagents() {
|
||||
fake_shell(),
|
||||
Some("2026-02-26".to_string()),
|
||||
Some("America/Los_Angeles".to_string()),
|
||||
None,
|
||||
/*network*/ None,
|
||||
Some("- agent-1: atlas\n- agent-2".to_string()),
|
||||
);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fn parses_user_message_with_text_and_two_images() {
|
||||
#[test]
|
||||
fn skips_local_image_label_text() {
|
||||
let image_url = "data:image/png;base64,abc".to_string();
|
||||
let label = codex_protocol::models::local_image_open_tag_text(1);
|
||||
let label = codex_protocol::models::local_image_open_tag_text(/*label_number*/ 1);
|
||||
let user_text = "Please review this image.".to_string();
|
||||
|
||||
let item = ResponseItem::Message {
|
||||
|
||||
@@ -121,7 +121,7 @@ fn populate_env_inserts_thread_id() {
|
||||
fn populate_env_omits_thread_id_when_missing() {
|
||||
let vars = make_vars(&[("PATH", "/usr/bin")]);
|
||||
let policy = ShellEnvironmentPolicy::default();
|
||||
let result = populate_env(vars, &policy, None);
|
||||
let result = populate_env(vars, &policy, /*thread_id*/ None);
|
||||
|
||||
let expected: HashMap<String, String> = hashmap! {
|
||||
"PATH".to_string() => "/usr/bin".to_string(),
|
||||
|
||||
@@ -115,7 +115,10 @@ async fn child_uses_parent_exec_policy_when_non_exec_policy_layers_differ() {
|
||||
let mut child_config = parent_config.clone();
|
||||
let mut layers: Vec<_> = child_config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
@@ -156,7 +159,10 @@ async fn child_does_not_use_parent_exec_policy_when_requirements_exec_policy_dif
|
||||
child_config.config_layer_stack = ConfigLayerStack::new(
|
||||
child_config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
@@ -291,7 +297,7 @@ async fn merges_requirements_exec_policy_network_rules() -> anyhow::Result<()> {
|
||||
"blocked.example.com",
|
||||
codex_execpolicy::NetworkRuleProtocol::Https,
|
||||
Decision::Forbidden,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)?;
|
||||
|
||||
let requirements = ConfigRequirements {
|
||||
@@ -338,7 +344,7 @@ host_executable(name = "git", paths = ["{git_path_literal}"])
|
||||
"blocked.example.com",
|
||||
codex_execpolicy::NetworkRuleProtocol::Https,
|
||||
Decision::Forbidden,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)?;
|
||||
|
||||
let requirements = ConfigRequirements {
|
||||
@@ -805,7 +811,7 @@ fn unmatched_granular_policy_still_prompts_for_restricted_sandbox_escalation() {
|
||||
&read_only_file_system_sandbox_policy(),
|
||||
&command,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
false,
|
||||
/*used_complex_parsing*/ false,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -823,7 +829,7 @@ fn unmatched_on_request_uses_split_filesystem_policy_for_escalation_prompts() {
|
||||
&restricted_file_system_policy,
|
||||
&command,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
false,
|
||||
/*used_complex_parsing*/ false,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1348,7 +1354,7 @@ fn derive_requested_execpolicy_amendment_for_test(
|
||||
fn derive_requested_execpolicy_amendment_returns_none_for_missing_prefix_rule() {
|
||||
assert_eq!(
|
||||
None,
|
||||
derive_requested_execpolicy_amendment_for_test(None, &[])
|
||||
derive_requested_execpolicy_amendment_for_test(/*prefix_rule*/ None, &[])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ fn make_exec_output(
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_requires_keywords() {
|
||||
let output = make_exec_output(1, "", "", "");
|
||||
let output = make_exec_output(/*exit_code*/ 1, "", "", "");
|
||||
assert!(!is_likely_sandbox_denied(
|
||||
SandboxType::LinuxSeccomp,
|
||||
&output
|
||||
@@ -33,13 +33,13 @@ fn sandbox_detection_requires_keywords() {
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_identifies_keyword_in_stderr() {
|
||||
let output = make_exec_output(1, "", "Operation not permitted", "");
|
||||
let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");
|
||||
assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_respects_quick_reject_exit_codes() {
|
||||
let output = make_exec_output(127, "", "command not found", "");
|
||||
let output = make_exec_output(/*exit_code*/ 127, "", "command not found", "");
|
||||
assert!(!is_likely_sandbox_denied(
|
||||
SandboxType::LinuxSeccomp,
|
||||
&output
|
||||
@@ -48,14 +48,14 @@ fn sandbox_detection_respects_quick_reject_exit_codes() {
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_ignores_non_sandbox_mode() {
|
||||
let output = make_exec_output(1, "", "Operation not permitted", "");
|
||||
let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");
|
||||
assert!(!is_likely_sandbox_denied(SandboxType::None, &output));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {
|
||||
let output = make_exec_output(
|
||||
0,
|
||||
/*exit_code*/ 0,
|
||||
"",
|
||||
"",
|
||||
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","reason":"not_allowed","source":"decider","protocol":"http","host":"google.com","port":80}"#,
|
||||
@@ -66,7 +66,7 @@ fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {
|
||||
#[test]
|
||||
fn sandbox_detection_uses_aggregated_output() {
|
||||
let output = make_exec_output(
|
||||
101,
|
||||
/*exit_code*/ 101,
|
||||
"",
|
||||
"",
|
||||
"cargo failed: Read-only file system when writing target",
|
||||
@@ -80,7 +80,7 @@ fn sandbox_detection_uses_aggregated_output() {
|
||||
#[test]
|
||||
fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {
|
||||
let output = make_exec_output(
|
||||
0,
|
||||
/*exit_code*/ 0,
|
||||
"",
|
||||
"",
|
||||
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","source":"decider","protocol":"http","host":"google.com","port":80}"#,
|
||||
@@ -100,9 +100,14 @@ async fn read_output_limits_retained_bytes_for_shell_capture() {
|
||||
writer.write_all(&bytes).await.expect("write");
|
||||
});
|
||||
|
||||
let out = read_output(reader, None, false, Some(EXEC_OUTPUT_MAX_BYTES))
|
||||
.await
|
||||
.expect("read");
|
||||
let out = read_output(
|
||||
reader,
|
||||
/*stream*/ None,
|
||||
/*is_stderr*/ false,
|
||||
Some(EXEC_OUTPUT_MAX_BYTES),
|
||||
)
|
||||
.await
|
||||
.expect("read");
|
||||
assert_eq!(out.text.len(), EXEC_OUTPUT_MAX_BYTES);
|
||||
}
|
||||
|
||||
@@ -196,7 +201,11 @@ async fn read_output_retains_all_bytes_for_full_buffer_capture() {
|
||||
writer.write_all(&bytes).await.expect("write");
|
||||
});
|
||||
|
||||
let out = read_output(reader, None, false, None).await.expect("read");
|
||||
let out = read_output(
|
||||
reader, /*stream*/ None, /*is_stderr*/ false, /*max_bytes*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("read");
|
||||
assert_eq!(out.text.len(), expected_len);
|
||||
}
|
||||
|
||||
@@ -211,7 +220,7 @@ fn aggregate_output_keeps_all_bytes_when_uncapped() {
|
||||
truncated_after_lines: None,
|
||||
};
|
||||
|
||||
let aggregated = aggregate_output(&stdout, &stderr, None);
|
||||
let aggregated = aggregate_output(&stdout, &stderr, /*max_bytes*/ None);
|
||||
|
||||
assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES * 2);
|
||||
assert_eq!(
|
||||
@@ -362,8 +371,8 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
cwd.as_path(),
|
||||
&None,
|
||||
false,
|
||||
None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -675,14 +684,15 @@ fn windows_elevated_rejects_split_write_read_carveouts() {
|
||||
|
||||
#[test]
|
||||
fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions() {
|
||||
let expected = codex_sandboxing::get_platform_sandbox(false).unwrap_or(SandboxType::None);
|
||||
let expected = codex_sandboxing::get_platform_sandbox(/*windows_sandbox_enabled*/ false)
|
||||
.unwrap_or(SandboxType::None);
|
||||
|
||||
assert_eq!(
|
||||
select_process_exec_tool_sandbox_type(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
|
||||
false,
|
||||
/*enforce_managed_network*/ false,
|
||||
),
|
||||
expected
|
||||
);
|
||||
@@ -736,8 +746,8 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
|
||||
&FileSystemSandboxPolicy::from(&SandboxPolicy::new_read_only_policy()),
|
||||
None,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
None,
|
||||
None,
|
||||
/*stdout_stream*/ None,
|
||||
/*after_spawn*/ None,
|
||||
)
|
||||
.await?;
|
||||
assert!(output.timed_out);
|
||||
@@ -798,8 +808,8 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
cwd.as_path(),
|
||||
&None,
|
||||
false,
|
||||
None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
)
|
||||
.await;
|
||||
let output = match result {
|
||||
|
||||
@@ -390,7 +390,7 @@ fn import_skills_returns_only_new_skill_directory_count() {
|
||||
fs::create_dir_all(agents_skills.join("skill-a")).expect("create existing target");
|
||||
|
||||
let copied_count = service_for_paths(claude_home, codex_home)
|
||||
.import_skills(None)
|
||||
.import_skills(/*cwd*/ None)
|
||||
.expect("import skills");
|
||||
|
||||
assert_eq!(copied_count, 1);
|
||||
|
||||
@@ -115,10 +115,10 @@ fn is_mutating_event_filters_non_mutating_event_kinds() {
|
||||
fn register_dedupes_by_path_and_scope() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (subscriber, _rx) = watcher.add_subscriber();
|
||||
let _first = subscriber.register_path(path("/tmp/skills"), false);
|
||||
let _second = subscriber.register_path(path("/tmp/skills"), false);
|
||||
let _third = subscriber.register_path(path("/tmp/skills"), true);
|
||||
let _fourth = subscriber.register_path(path("/tmp/other-skills"), true);
|
||||
let _first = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
|
||||
let _second = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
|
||||
let _third = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
|
||||
let _fourth = subscriber.register_path(path("/tmp/other-skills"), /*recursive*/ true);
|
||||
|
||||
assert_eq!(
|
||||
watcher.watch_counts_for_test(&path("/tmp/skills")),
|
||||
@@ -134,7 +134,7 @@ fn register_dedupes_by_path_and_scope() {
|
||||
fn watch_registration_drop_unregisters_paths() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (subscriber, _rx) = watcher.add_subscriber();
|
||||
let registration = subscriber.register_path(path("/tmp/skills"), true);
|
||||
let registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
|
||||
|
||||
drop(registration);
|
||||
|
||||
@@ -146,7 +146,7 @@ fn subscriber_drop_unregisters_paths() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let registration = {
|
||||
let (subscriber, _rx) = watcher.add_subscriber();
|
||||
subscriber.register_path(path("/tmp/skills"), true)
|
||||
subscriber.register_path(path("/tmp/skills"), /*recursive*/ true)
|
||||
};
|
||||
|
||||
assert_eq!(watcher.watch_counts_for_test(&path("/tmp/skills")), None);
|
||||
@@ -174,8 +174,8 @@ fn recursive_registration_downgrades_to_non_recursive_after_drop() {
|
||||
|
||||
let watcher = Arc::new(FileWatcher::new().expect("watcher"));
|
||||
let (subscriber, _rx) = watcher.add_subscriber();
|
||||
let non_recursive = subscriber.register_path(root.clone(), false);
|
||||
let recursive = subscriber.register_path(root.clone(), true);
|
||||
let non_recursive = subscriber.register_path(root.clone(), /*recursive*/ false);
|
||||
let recursive = subscriber.register_path(root.clone(), /*recursive*/ true);
|
||||
|
||||
{
|
||||
let inner = watcher.inner.as_ref().expect("watcher inner");
|
||||
@@ -209,7 +209,7 @@ fn unregister_holds_state_lock_until_unwatch_finishes() {
|
||||
let watcher = Arc::new(FileWatcher::new().expect("watcher"));
|
||||
let (unregister_subscriber, _unregister_rx) = watcher.add_subscriber();
|
||||
let (register_subscriber, _register_rx) = watcher.add_subscriber();
|
||||
let registration = unregister_subscriber.register_path(root.clone(), true);
|
||||
let registration = unregister_subscriber.register_path(root.clone(), /*recursive*/ true);
|
||||
|
||||
let inner = watcher.inner.as_ref().expect("watcher inner");
|
||||
let inner_guard = inner.lock().expect("inner lock");
|
||||
@@ -229,7 +229,8 @@ fn unregister_holds_state_lock_until_unwatch_finishes() {
|
||||
|
||||
let register_root = root.clone();
|
||||
let register_thread = std::thread::spawn(move || {
|
||||
let registration = register_subscriber.register_path(register_root, false);
|
||||
let registration =
|
||||
register_subscriber.register_path(register_root, /*recursive*/ false);
|
||||
(register_subscriber, registration)
|
||||
});
|
||||
|
||||
@@ -257,8 +258,8 @@ async fn matching_subscribers_are_notified() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (skills_subscriber, skills_rx) = watcher.add_subscriber();
|
||||
let (plugins_subscriber, plugins_rx) = watcher.add_subscriber();
|
||||
let _skills = skills_subscriber.register_path(path("/tmp/skills"), true);
|
||||
let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), true);
|
||||
let _skills = skills_subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
|
||||
let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), /*recursive*/ true);
|
||||
let mut skills_rx = ThrottledWatchReceiver::new(skills_rx, TEST_THROTTLE_INTERVAL);
|
||||
let mut plugins_rx = ThrottledWatchReceiver::new(plugins_rx, TEST_THROTTLE_INTERVAL);
|
||||
|
||||
@@ -285,7 +286,7 @@ async fn matching_subscribers_are_notified() {
|
||||
async fn non_recursive_watch_ignores_grandchildren() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (subscriber, rx) = watcher.add_subscriber();
|
||||
let _registration = subscriber.register_path(path("/tmp/skills"), false);
|
||||
let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false);
|
||||
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
|
||||
|
||||
watcher
|
||||
@@ -300,7 +301,8 @@ async fn non_recursive_watch_ignores_grandchildren() {
|
||||
async fn ancestor_events_notify_child_watches() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (subscriber, rx) = watcher.add_subscriber();
|
||||
let _registration = subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), false);
|
||||
let _registration =
|
||||
subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), /*recursive*/ false);
|
||||
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
|
||||
|
||||
watcher.send_paths_for_test(vec![path("/tmp/skills")]).await;
|
||||
@@ -321,7 +323,7 @@ async fn ancestor_events_notify_child_watches() {
|
||||
async fn spawn_event_loop_filters_non_mutating_events() {
|
||||
let watcher = Arc::new(FileWatcher::noop());
|
||||
let (subscriber, rx) = watcher.add_subscriber();
|
||||
let _registration = subscriber.register_path(path("/tmp/skills"), true);
|
||||
let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true);
|
||||
let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL);
|
||||
let (raw_tx, raw_rx) = mpsc::unbounded_channel();
|
||||
watcher.spawn_event_loop_for_test(raw_rx);
|
||||
|
||||
@@ -72,7 +72,7 @@ async fn create_test_git_repo(temp_dir: &TempDir) -> PathBuf {
|
||||
#[tokio::test]
|
||||
async fn test_recent_commits_non_git_directory_returns_empty() {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let entries = recent_commits(temp_dir.path(), 10).await;
|
||||
let entries = recent_commits(temp_dir.path(), /*limit*/ 10).await;
|
||||
assert!(entries.is_empty(), "expected no commits outside a git repo");
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ async fn test_recent_commits_orders_and_limits() {
|
||||
.expect("git commit 3");
|
||||
|
||||
// Request the latest 3 commits; should be our three changes in reverse time order.
|
||||
let entries = recent_commits(&repo_path, 3).await;
|
||||
let entries = recent_commits(&repo_path, /*limit*/ 3).await;
|
||||
assert_eq!(entries.len(), 3);
|
||||
assert_eq!(entries[0].subject, "third change");
|
||||
assert_eq!(entries[1].subject, "second change");
|
||||
|
||||
@@ -746,9 +746,13 @@ mod tests {
|
||||
#[test]
|
||||
fn guardian_review_session_config_change_invalidates_cached_session() {
|
||||
let parent_config = crate::config::test_config();
|
||||
let cached_spawn_config =
|
||||
build_guardian_review_session_config(&parent_config, None, "active-model", None)
|
||||
.expect("cached guardian config");
|
||||
let cached_spawn_config = build_guardian_review_session_config(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("cached guardian config");
|
||||
let cached_reuse_key =
|
||||
GuardianReviewSessionReuseKey::from_spawn_config(&cached_spawn_config);
|
||||
|
||||
@@ -757,9 +761,9 @@ mod tests {
|
||||
Some("https://guardian.example.invalid/v1".to_string());
|
||||
let next_spawn_config = build_guardian_review_session_config(
|
||||
&changed_parent_config,
|
||||
None,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
None,
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("next guardian config");
|
||||
let next_reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(&next_spawn_config);
|
||||
@@ -775,7 +779,7 @@ mod tests {
|
||||
async fn run_before_review_deadline_times_out_before_future_completes() {
|
||||
let outcome = run_before_review_deadline(
|
||||
tokio::time::Instant::now() + Duration::from_millis(10),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
async {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
},
|
||||
@@ -816,7 +820,7 @@ mod tests {
|
||||
|
||||
let outcome = run_before_review_deadline_with_cancel(
|
||||
tokio::time::Instant::now() + Duration::from_millis(10),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
&cancel_token,
|
||||
async {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
@@ -862,7 +866,7 @@ mod tests {
|
||||
|
||||
let outcome = run_before_review_deadline_with_cancel(
|
||||
tokio::time::Instant::now() + Duration::from_secs(1),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
&cancel_token,
|
||||
async { 42usize },
|
||||
)
|
||||
|
||||
@@ -251,7 +251,7 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
|
||||
fn guardian_truncate_text_keeps_prefix_suffix_and_xml_marker() {
|
||||
let content = "prefix ".repeat(200) + &" suffix".repeat(200);
|
||||
|
||||
let truncated = guardian_truncate_text(&content, 20);
|
||||
let truncated = guardian_truncate_text(&content, /*token_cap*/ 20);
|
||||
|
||||
assert!(truncated.starts_with("prefix"));
|
||||
assert!(truncated.contains("<truncated omitted_approx_tokens=\""));
|
||||
@@ -392,7 +392,7 @@ async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
|
||||
patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"
|
||||
.to_string(),
|
||||
},
|
||||
None,
|
||||
/*retry_reason*/ None,
|
||||
cancel_token,
|
||||
)
|
||||
.await;
|
||||
@@ -556,7 +556,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
Arc::clone(&turn),
|
||||
prompt,
|
||||
guardian_output_schema(),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
)
|
||||
.await;
|
||||
let GuardianReviewOutcome::Completed(Ok(assessment)) = outcome else {
|
||||
@@ -634,7 +634,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
|
||||
Arc::clone(&turn),
|
||||
first_prompt,
|
||||
guardian_output_schema(),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
)
|
||||
.await;
|
||||
let second_prompt = build_guardian_prompt_items(
|
||||
@@ -659,7 +659,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow:
|
||||
Arc::clone(&turn),
|
||||
second_prompt,
|
||||
guardian_output_schema(),
|
||||
None,
|
||||
/*external_cancel*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -772,7 +772,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
|
||||
additional_permissions: None,
|
||||
justification: Some("Need to push the reviewed docs fix.".to_string()),
|
||||
},
|
||||
None,
|
||||
/*retry_reason*/ None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -884,7 +884,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a
|
||||
justification: Some("Inspect repo state before proceeding.".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
review_approval_request(&session, &turn, initial_request, None).await,
|
||||
review_approval_request(&session, &turn, initial_request, /*retry_reason*/ None).await,
|
||||
ReviewDecision::Approved
|
||||
);
|
||||
|
||||
@@ -988,7 +988,7 @@ fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
None,
|
||||
/*live_network_config*/ None,
|
||||
"parent-active-model",
|
||||
Some(codex_protocol::openai_models::ReasoningEffort::Low),
|
||||
)
|
||||
@@ -1019,9 +1019,13 @@ fn guardian_review_session_config_overrides_parent_developer_instructions() {
|
||||
parent_config.developer_instructions =
|
||||
Some("parent or managed config should not replace guardian policy".to_string());
|
||||
|
||||
let guardian_config =
|
||||
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
|
||||
.expect("guardian config");
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
assert_eq!(
|
||||
guardian_config.developer_instructions,
|
||||
@@ -1040,7 +1044,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
parent_config.permissions.network = Some(
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
parent_network,
|
||||
None,
|
||||
/*requirements*/ None,
|
||||
parent_config.permissions.sandbox_policy.get(),
|
||||
)
|
||||
.expect("parent network proxy spec"),
|
||||
@@ -1056,7 +1060,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
&parent_config,
|
||||
Some(live_network.clone()),
|
||||
"active-model",
|
||||
None,
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
@@ -1065,7 +1069,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
Some(
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
live_network,
|
||||
None,
|
||||
/*requirements*/ None,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.expect("live network proxy spec")
|
||||
@@ -1087,9 +1091,13 @@ fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
)
|
||||
.expect("managed features");
|
||||
|
||||
let err =
|
||||
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
|
||||
.expect_err("guardian config should fail when collab is pinned on");
|
||||
let err = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect_err("guardian config should fail when collab is pinned on");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
@@ -1102,9 +1110,13 @@ fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_
|
||||
let mut parent_config = test_config();
|
||||
parent_config.model = Some("configured-model".to_string());
|
||||
|
||||
let guardian_config =
|
||||
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
|
||||
.expect("guardian config");
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
assert_eq!(guardian_config.model, Some("active-model".to_string()));
|
||||
}
|
||||
@@ -1135,9 +1147,13 @@ fn guardian_review_session_config_uses_requirements_guardian_override() {
|
||||
)
|
||||
.expect("load config");
|
||||
|
||||
let guardian_config =
|
||||
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
|
||||
.expect("guardian config");
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
assert_eq!(
|
||||
guardian_config.developer_instructions,
|
||||
@@ -1163,9 +1179,13 @@ fn guardian_review_session_config_uses_default_guardian_policy_without_requireme
|
||||
)
|
||||
.expect("load config");
|
||||
|
||||
let guardian_config =
|
||||
build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None)
|
||||
.expect("guardian config");
|
||||
let guardian_config = build_guardian_review_session_config_for_test(
|
||||
&parent_config,
|
||||
/*live_network_config*/ None,
|
||||
"active-model",
|
||||
/*reasoning_effort*/ None,
|
||||
)
|
||||
.expect("guardian config");
|
||||
|
||||
assert_eq!(
|
||||
guardian_config.developer_instructions,
|
||||
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolve_oauth_scopes_prefers_configured_over_discovered() {
|
||||
let resolved = resolve_oauth_scopes(
|
||||
None,
|
||||
/*explicit_scopes*/ None,
|
||||
Some(vec!["configured".to_string()]),
|
||||
Some(vec!["discovered".to_string()]),
|
||||
);
|
||||
@@ -225,7 +225,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_oauth_scopes_uses_discovered_when_needed() {
|
||||
let resolved = resolve_oauth_scopes(None, None, Some(vec!["discovered".to_string()]));
|
||||
let resolved = resolve_oauth_scopes(
|
||||
/*explicit_scopes*/ None,
|
||||
/*configured_scopes*/ None,
|
||||
Some(vec!["discovered".to_string()]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolved,
|
||||
@@ -238,7 +242,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes() {
|
||||
let resolved = resolve_oauth_scopes(None, Some(Vec::new()), Some(vec!["ignored".into()]));
|
||||
let resolved = resolve_oauth_scopes(
|
||||
/*explicit_scopes*/ None,
|
||||
Some(Vec::new()),
|
||||
Some(vec!["ignored".into()]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolved,
|
||||
@@ -251,7 +259,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_oauth_scopes_falls_back_to_empty() {
|
||||
let resolved = resolve_oauth_scopes(None, None, None);
|
||||
let resolved = resolve_oauth_scopes(
|
||||
/*explicit_scopes*/ None, /*configured_scopes*/ None,
|
||||
/*discovered_scopes*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolved,
|
||||
|
||||
@@ -167,7 +167,12 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
let mut config = crate::config::test_config();
|
||||
config.chatgpt_base_url = "https://chatgpt.com".to_string();
|
||||
|
||||
let mut servers = with_codex_apps_mcp(HashMap::new(), false, None, &config);
|
||||
let mut servers = with_codex_apps_mcp(
|
||||
HashMap::new(),
|
||||
/*connectors_enabled*/ false,
|
||||
/*auth*/ None,
|
||||
&config,
|
||||
);
|
||||
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
||||
|
||||
config
|
||||
@@ -175,7 +180,9 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() {
|
||||
.enable(Feature::Apps)
|
||||
.expect("test config should allow apps");
|
||||
|
||||
servers = with_codex_apps_mcp(servers, true, None, &config);
|
||||
servers = with_codex_apps_mcp(
|
||||
servers, /*connectors_enabled*/ true, /*auth*/ None, &config,
|
||||
);
|
||||
let server = servers
|
||||
.get(CODEX_APPS_MCP_SERVER_NAME)
|
||||
.expect("codex apps should be present when apps is enabled");
|
||||
@@ -252,7 +259,7 @@ async fn effective_mcp_servers_include_plugins_without_overriding_user_config()
|
||||
.expect("test config should accept MCP servers");
|
||||
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
|
||||
let effective = mcp_manager.effective_servers(&config, None);
|
||||
let effective = mcp_manager.effective_servers(&config, /*auth*/ None);
|
||||
|
||||
let sample = effective.get("sample").expect("user server should exist");
|
||||
let docs = effective.get("docs").expect("plugin server should exist");
|
||||
|
||||
@@ -562,7 +562,7 @@ fn mcp_init_error_display_prompts_for_login_when_auth_required() {
|
||||
let server_name = "example";
|
||||
let err: StartupOutcomeError = anyhow::anyhow!("Auth required for server").into();
|
||||
|
||||
let display = mcp_init_error_display(server_name, None, &err);
|
||||
let display = mcp_init_error_display(server_name, /*entry*/ None, &err);
|
||||
|
||||
let expected = format!(
|
||||
"The {server_name} MCP server is not logged in. Run `codex mcp login {server_name}`."
|
||||
@@ -609,7 +609,7 @@ fn mcp_init_error_display_includes_startup_timeout_hint() {
|
||||
let server_name = "slow";
|
||||
let err: StartupOutcomeError = anyhow::anyhow!("request timed out").into();
|
||||
|
||||
let display = mcp_init_error_display(server_name, None, &err);
|
||||
let display = mcp_init_error_display(server_name, /*entry*/ None, &err);
|
||||
|
||||
assert_eq!(
|
||||
"MCP client for `slow` timed out after 10 seconds. Add or adjust `startup_timeout_sec` in your config.toml:\n[mcp_servers.slow]\nstartup_timeout_sec = XX",
|
||||
|
||||
@@ -330,7 +330,7 @@ mod tests {
|
||||
&templates,
|
||||
"codex_apps",
|
||||
Some("github"),
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
Some("add_comment"),
|
||||
Some(&json!({})),
|
||||
);
|
||||
@@ -361,7 +361,7 @@ mod tests {
|
||||
&templates,
|
||||
"codex_apps",
|
||||
Some("calendar"),
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
Some("create_event"),
|
||||
Some(&json!({})),
|
||||
),
|
||||
|
||||
@@ -72,13 +72,13 @@ fn prompt_options(
|
||||
|
||||
#[test]
|
||||
fn approval_required_when_read_only_false_and_destructive() {
|
||||
let annotations = annotations(Some(false), Some(true), None);
|
||||
let annotations = annotations(Some(false), Some(true), /*open_world*/ None);
|
||||
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_required_when_read_only_false_and_open_world() {
|
||||
let annotations = annotations(Some(false), None, Some(true));
|
||||
let annotations = annotations(Some(false), /*destructive*/ None, Some(true));
|
||||
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true);
|
||||
}
|
||||
|
||||
@@ -90,12 +90,16 @@ fn approval_required_when_destructive_even_if_read_only_true() {
|
||||
|
||||
#[test]
|
||||
fn approval_required_when_annotations_are_absent() {
|
||||
assert_eq!(requires_mcp_tool_approval(None), true);
|
||||
assert_eq!(requires_mcp_tool_approval(/*annotations*/ None), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_not_required_when_read_only_and_other_hints_are_absent() {
|
||||
let annotations = annotations(Some(true), None, None);
|
||||
let annotations = annotations(
|
||||
Some(true),
|
||||
/*destructive*/ None,
|
||||
/*open_world*/ None,
|
||||
);
|
||||
assert_eq!(requires_mcp_tool_approval(Some(&annotations)), false);
|
||||
}
|
||||
|
||||
@@ -187,7 +191,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"create_event",
|
||||
Some("Calendar"),
|
||||
prompt_options(true, true),
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
|
||||
),
|
||||
Some("Allow Calendar to create an event?"),
|
||||
);
|
||||
|
||||
@@ -221,7 +227,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p
|
||||
]),
|
||||
question,
|
||||
message_override: Some("Allow Calendar to create an event?"),
|
||||
prompt_options: prompt_options(true, true),
|
||||
prompt_options: prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -279,9 +287,11 @@ fn custom_mcp_tool_question_mentions_server_name() {
|
||||
"q".to_string(),
|
||||
"custom_server",
|
||||
"run_action",
|
||||
None,
|
||||
prompt_options(false, false),
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false,
|
||||
),
|
||||
/*question_override*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(question.header, "Approve app tool call?");
|
||||
@@ -305,9 +315,11 @@ fn codex_apps_tool_question_uses_fallback_app_label() {
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
None,
|
||||
prompt_options(true, true),
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
|
||||
),
|
||||
/*question_override*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -323,8 +335,10 @@ fn trusted_codex_apps_tool_question_offers_always_allow() {
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
Some("Calendar"),
|
||||
prompt_options(true, true),
|
||||
None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
|
||||
),
|
||||
/*question_override*/ None,
|
||||
);
|
||||
let options = question.options.expect("options");
|
||||
|
||||
@@ -363,8 +377,12 @@ fn codex_apps_tool_question_without_elicitation_omits_always_allow() {
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
Some("Calendar"),
|
||||
mcp_tool_approval_prompt_options(Some(&session_key), Some(&persistent_key), false),
|
||||
None,
|
||||
mcp_tool_approval_prompt_options(
|
||||
Some(&session_key),
|
||||
Some(&persistent_key),
|
||||
/*tool_call_mcp_elicitation_enabled*/ false,
|
||||
),
|
||||
/*question_override*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -388,9 +406,11 @@ fn custom_mcp_tool_question_offers_session_remember_and_always_allow() {
|
||||
"q".to_string(),
|
||||
"custom_server",
|
||||
"run_action",
|
||||
None,
|
||||
prompt_options(true, true),
|
||||
None,
|
||||
/*connector_name*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true,
|
||||
),
|
||||
/*question_override*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -423,11 +443,15 @@ fn custom_servers_support_session_and_persistent_approval() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
session_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto),
|
||||
session_mcp_tool_approval_key(&invocation, /*metadata*/ None, AppToolApproval::Auto),
|
||||
Some(expected.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
persistent_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto),
|
||||
persistent_mcp_tool_approval_key(
|
||||
&invocation,
|
||||
/*metadata*/ None,
|
||||
AppToolApproval::Auto
|
||||
),
|
||||
Some(expected)
|
||||
);
|
||||
}
|
||||
@@ -439,7 +463,13 @@ fn codex_apps_connectors_support_persistent_approval() {
|
||||
tool: "calendar/list_events".to_string(),
|
||||
arguments: None,
|
||||
};
|
||||
let metadata = approval_metadata(Some("calendar"), Some("Calendar"), None, None, None);
|
||||
let metadata = approval_metadata(
|
||||
Some("calendar"),
|
||||
Some("Calendar"),
|
||||
/*connector_description*/ None,
|
||||
/*tool_title*/ None,
|
||||
/*tool_description*/ None,
|
||||
);
|
||||
let expected = McpToolApprovalKey {
|
||||
server: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
@@ -475,7 +505,8 @@ fn sanitize_mcp_tool_result_for_model_rewrites_image_content() {
|
||||
meta: None,
|
||||
});
|
||||
|
||||
let got = sanitize_mcp_tool_result_for_model(false, result).expect("sanitized result");
|
||||
let got = sanitize_mcp_tool_result_for_model(/*supports_image_input*/ false, result)
|
||||
.expect("sanitized result");
|
||||
|
||||
assert_eq!(
|
||||
got.content,
|
||||
@@ -505,8 +536,11 @@ fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported() {
|
||||
meta: Some(serde_json::json!({"k": "v"})),
|
||||
};
|
||||
|
||||
let got =
|
||||
sanitize_mcp_tool_result_for_model(true, Ok(original.clone())).expect("unsanitized result");
|
||||
let got = sanitize_mcp_tool_result_for_model(
|
||||
/*supports_image_input*/ true,
|
||||
Ok(original.clone()),
|
||||
)
|
||||
.expect("unsanitized result");
|
||||
|
||||
assert_eq!(got, original);
|
||||
}
|
||||
@@ -606,10 +640,12 @@ fn approval_elicitation_meta_marks_tool_approvals() {
|
||||
assert_eq!(
|
||||
build_mcp_tool_approval_elicitation_meta(
|
||||
"custom_server",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
prompt_options(false, false),
|
||||
/*metadata*/ None,
|
||||
/*tool_params*/ None,
|
||||
/*tool_params_display*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false
|
||||
),
|
||||
),
|
||||
Some(serde_json::json!({
|
||||
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
|
||||
@@ -623,15 +659,17 @@ fn approval_elicitation_meta_merges_session_and_always_persist_for_custom_server
|
||||
build_mcp_tool_approval_elicitation_meta(
|
||||
"custom_server",
|
||||
Some(&approval_metadata(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*connector_id*/ None,
|
||||
/*connector_name*/ None,
|
||||
/*connector_description*/ None,
|
||||
Some("Run Action"),
|
||||
Some("Runs the selected action."),
|
||||
)),
|
||||
Some(&serde_json::json!({"id": 1})),
|
||||
None,
|
||||
prompt_options(true, true),
|
||||
/*tool_params_display*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true
|
||||
),
|
||||
),
|
||||
Some(serde_json::json!({
|
||||
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
|
||||
@@ -742,11 +780,11 @@ fn prepare_arc_request_action_serializes_mcp_tool_call_shape() {
|
||||
let action = prepare_arc_request_action(
|
||||
&invocation,
|
||||
Some(&approval_metadata(
|
||||
None,
|
||||
/*connector_id*/ None,
|
||||
Some("Playwright"),
|
||||
None,
|
||||
/*connector_description*/ None,
|
||||
Some("Navigate"),
|
||||
None,
|
||||
/*tool_description*/ None,
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -796,8 +834,10 @@ fn approval_elicitation_meta_includes_connector_source_for_codex_apps() {
|
||||
Some(&serde_json::json!({
|
||||
"calendar_id": "primary",
|
||||
})),
|
||||
None,
|
||||
prompt_options(false, false),
|
||||
/*tool_params_display*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ false, /*allow_persistent_approval*/ false
|
||||
),
|
||||
),
|
||||
Some(serde_json::json!({
|
||||
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
|
||||
@@ -829,8 +869,10 @@ fn approval_elicitation_meta_merges_session_and_always_persist_with_connector_so
|
||||
Some(&serde_json::json!({
|
||||
"calendar_id": "primary",
|
||||
})),
|
||||
None,
|
||||
prompt_options(true, true),
|
||||
/*tool_params_display*/ None,
|
||||
prompt_options(
|
||||
/*allow_session_remember*/ true, /*allow_persistent_approval*/ true
|
||||
),
|
||||
),
|
||||
Some(serde_json::json!({
|
||||
MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL,
|
||||
@@ -1170,7 +1212,11 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() {
|
||||
arguments: None,
|
||||
};
|
||||
let metadata = McpToolApprovalMetadata {
|
||||
annotations: Some(annotations(Some(true), None, None)),
|
||||
annotations: Some(annotations(
|
||||
Some(true),
|
||||
/*destructive*/ None,
|
||||
/*open_world*/ None,
|
||||
)),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
@@ -1233,7 +1279,11 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() {
|
||||
arguments: None,
|
||||
};
|
||||
let metadata = McpToolApprovalMetadata {
|
||||
annotations: Some(annotations(Some(true), None, None)),
|
||||
annotations: Some(annotations(
|
||||
Some(true),
|
||||
/*destructive*/ None,
|
||||
/*open_world*/ None,
|
||||
)),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
@@ -1268,7 +1318,11 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval
|
||||
arguments: None,
|
||||
};
|
||||
let metadata = McpToolApprovalMetadata {
|
||||
annotations: Some(annotations(Some(true), None, None)),
|
||||
annotations: Some(annotations(
|
||||
Some(true),
|
||||
/*destructive*/ None,
|
||||
/*open_world*/ None,
|
||||
)),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
|
||||
@@ -29,7 +29,7 @@ fn fixed_thread_id() -> ThreadId {
|
||||
#[test]
|
||||
fn rollout_summary_file_stem_uses_uuid_timestamp_and_hash_when_slug_missing() {
|
||||
let thread_id = fixed_thread_id();
|
||||
let memory = stage1_output_with_slug(thread_id, None);
|
||||
let memory = stage1_output_with_slug(thread_id, /*rollout_slug*/ None);
|
||||
|
||||
assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX);
|
||||
assert_eq!(
|
||||
|
||||
@@ -526,8 +526,8 @@ mod phase2 {
|
||||
thread_id,
|
||||
self.session.conversation_id,
|
||||
source_updated_at,
|
||||
3_600,
|
||||
64,
|
||||
/*lease_seconds*/ 3_600,
|
||||
/*max_running_jobs*/ 64,
|
||||
)
|
||||
.await
|
||||
.expect("claim stage-1 job");
|
||||
@@ -543,7 +543,7 @@ mod phase2 {
|
||||
source_updated_at,
|
||||
"raw memory",
|
||||
"rollout summary",
|
||||
None,
|
||||
/*rollout_slug*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("mark stage-1 success"),
|
||||
@@ -571,24 +571,24 @@ mod phase2 {
|
||||
|
||||
#[test]
|
||||
fn completion_watermark_never_regresses_below_claimed_input_watermark() {
|
||||
let stage1_output = stage1_output_with_source_updated_at(123);
|
||||
let stage1_output = stage1_output_with_source_updated_at(/*source_updated_at*/ 123);
|
||||
|
||||
let completion = phase2::get_watermark(1_000, &[stage1_output]);
|
||||
let completion = phase2::get_watermark(/*claimed_watermark*/ 1_000, &[stage1_output]);
|
||||
pretty_assertions::assert_eq!(completion, 1_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completion_watermark_uses_claimed_watermark_when_there_are_no_memories() {
|
||||
let completion = phase2::get_watermark(777, &[]);
|
||||
let completion = phase2::get_watermark(/*claimed_watermark*/ 777, &[]);
|
||||
pretty_assertions::assert_eq!(completion, 777);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completion_watermark_uses_latest_memory_timestamp_when_it_is_newer() {
|
||||
let older = stage1_output_with_source_updated_at(123);
|
||||
let newer = stage1_output_with_source_updated_at(456);
|
||||
let older = stage1_output_with_source_updated_at(/*source_updated_at*/ 123);
|
||||
let newer = stage1_output_with_source_updated_at(/*source_updated_at*/ 456);
|
||||
|
||||
let completion = phase2::get_watermark(200, &[older, newer]);
|
||||
let completion = phase2::get_watermark(/*claimed_watermark*/ 200, &[older, newer]);
|
||||
pretty_assertions::assert_eq!(completion, 456);
|
||||
}
|
||||
|
||||
@@ -608,12 +608,12 @@ mod phase2 {
|
||||
let harness = DispatchHarness::new().await;
|
||||
harness
|
||||
.state_db
|
||||
.enqueue_global_consolidation(123)
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 123)
|
||||
.await
|
||||
.expect("enqueue global consolidation");
|
||||
let claimed = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim running global lock");
|
||||
assert!(
|
||||
@@ -625,7 +625,7 @@ mod phase2 {
|
||||
|
||||
let running_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim while lock is still running");
|
||||
pretty_assertions::assert_eq!(running_claim, Phase2JobClaimOutcome::SkippedRunning);
|
||||
@@ -641,7 +641,7 @@ mod phase2 {
|
||||
|
||||
let stale_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 0)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 0)
|
||||
.await
|
||||
.expect("claim stale global lock");
|
||||
assert!(
|
||||
@@ -653,7 +653,7 @@ mod phase2 {
|
||||
|
||||
let post_dispatch_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim after stale lock dispatch");
|
||||
assert!(
|
||||
@@ -759,7 +759,7 @@ mod phase2 {
|
||||
|
||||
harness
|
||||
.state_db
|
||||
.enqueue_global_consolidation(999)
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 999)
|
||||
.await
|
||||
.expect("enqueue global consolidation");
|
||||
|
||||
@@ -801,7 +801,7 @@ mod phase2 {
|
||||
);
|
||||
let next_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim global job after empty consolidation success");
|
||||
pretty_assertions::assert_eq!(next_claim, Phase2JobClaimOutcome::SkippedNotDirty);
|
||||
@@ -817,7 +817,7 @@ mod phase2 {
|
||||
let harness = DispatchHarness::new().await;
|
||||
harness
|
||||
.state_db
|
||||
.enqueue_global_consolidation(99)
|
||||
.enqueue_global_consolidation(/*input_watermark*/ 99)
|
||||
.await
|
||||
.expect("enqueue global consolidation");
|
||||
let mut constrained_config = harness.config.as_ref().clone();
|
||||
@@ -828,7 +828,7 @@ mod phase2 {
|
||||
|
||||
let retry_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim global job after sandbox policy failure");
|
||||
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
|
||||
@@ -840,7 +840,7 @@ mod phase2 {
|
||||
#[tokio::test]
|
||||
async fn dispatch_marks_job_for_retry_when_syncing_artifacts_fails() {
|
||||
let harness = DispatchHarness::new().await;
|
||||
harness.seed_stage1_output(100).await;
|
||||
harness.seed_stage1_output(/*source_updated_at*/ 100).await;
|
||||
let root = memory_root(&harness.config.codex_home);
|
||||
tokio::fs::write(&root, "not a directory")
|
||||
.await
|
||||
@@ -850,7 +850,7 @@ mod phase2 {
|
||||
|
||||
let retry_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim global job after sync failure");
|
||||
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
|
||||
@@ -862,7 +862,7 @@ mod phase2 {
|
||||
#[tokio::test]
|
||||
async fn dispatch_marks_job_for_retry_when_rebuilding_raw_memories_fails() {
|
||||
let harness = DispatchHarness::new().await;
|
||||
harness.seed_stage1_output(100).await;
|
||||
harness.seed_stage1_output(/*source_updated_at*/ 100).await;
|
||||
let root = memory_root(&harness.config.codex_home);
|
||||
tokio::fs::create_dir_all(raw_memories_file(&root))
|
||||
.await
|
||||
@@ -872,7 +872,7 @@ mod phase2 {
|
||||
|
||||
let retry_claim = harness
|
||||
.state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim global job after rebuild failure");
|
||||
pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty);
|
||||
@@ -917,7 +917,13 @@ mod phase2 {
|
||||
.expect("upsert thread metadata");
|
||||
|
||||
let claim = state_db
|
||||
.try_claim_stage1_job(thread_id, session.conversation_id, 100, 3_600, 64)
|
||||
.try_claim_stage1_job(
|
||||
thread_id,
|
||||
session.conversation_id,
|
||||
/*source_updated_at*/ 100,
|
||||
/*lease_seconds*/ 3_600,
|
||||
/*max_running_jobs*/ 64,
|
||||
)
|
||||
.await
|
||||
.expect("claim stage-1 job");
|
||||
let ownership_token = match claim {
|
||||
@@ -929,10 +935,10 @@ mod phase2 {
|
||||
.mark_stage1_job_succeeded(
|
||||
thread_id,
|
||||
&ownership_token,
|
||||
100,
|
||||
/*source_updated_at*/ 100,
|
||||
"raw memory",
|
||||
"rollout summary",
|
||||
None,
|
||||
/*rollout_slug*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("mark stage-1 success"),
|
||||
@@ -942,7 +948,7 @@ mod phase2 {
|
||||
phase2::run(&session, Arc::clone(&config)).await;
|
||||
|
||||
let retry_claim = state_db
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600)
|
||||
.await
|
||||
.expect("claim global job after spawn failure");
|
||||
pretty_assertions::assert_eq!(
|
||||
|
||||
@@ -37,8 +37,8 @@ async fn lookup_reads_history_entries() {
|
||||
let (log_id, count) = history_metadata_for_file(&history_path).await;
|
||||
assert_eq!(count, entries.len());
|
||||
|
||||
let second_entry =
|
||||
lookup_history_entry(&history_path, log_id, 1).expect("fetch second history entry");
|
||||
let second_entry = lookup_history_entry(&history_path, log_id, /*offset*/ 1)
|
||||
.expect("fetch second history entry");
|
||||
assert_eq!(second_entry, entries[1]);
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ async fn lookup_uses_stable_log_id_after_appends() {
|
||||
)
|
||||
.expect("append history entry");
|
||||
|
||||
let fetched =
|
||||
lookup_history_entry(&history_path, log_id, 1).expect("lookup appended history entry");
|
||||
let fetched = lookup_history_entry(&history_path, log_id, /*offset*/ 1)
|
||||
.expect("lookup appended history entry");
|
||||
assert_eq!(fetched, appended);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ fn default_mode_instructions_replace_mode_names_placeholder() {
|
||||
let expected_snippet = format!("Known mode names are {known_mode_names}.");
|
||||
assert!(default_instructions.contains(&expected_snippet));
|
||||
|
||||
let expected_availability_message =
|
||||
request_user_input_availability_message(ModeKind::Default, true);
|
||||
let expected_availability_message = request_user_input_availability_message(
|
||||
ModeKind::Default,
|
||||
/*default_mode_request_user_input*/ true,
|
||||
);
|
||||
assert!(default_instructions.contains(&expected_availability_message));
|
||||
assert!(default_instructions.contains("prefer using the `request_user_input` tool"));
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ async fn get_model_info_tracks_fallback_usage() {
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
auth_manager,
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
let known_slug = manager
|
||||
@@ -175,7 +175,7 @@ async fn get_model_info_uses_custom_catalog() {
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let mut overlay = remote_model("gpt-overlay", "Overlay", 0);
|
||||
let mut overlay = remote_model("gpt-overlay", "Overlay", /*priority*/ 0);
|
||||
overlay.supports_image_detail_original = true;
|
||||
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
@@ -208,7 +208,7 @@ async fn get_model_info_matches_namespaced_suffix() {
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let mut remote = remote_model("gpt-image", "Image", 0);
|
||||
let mut remote = remote_model("gpt-image", "Image", /*priority*/ 0);
|
||||
remote.supports_image_detail_original = true;
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(
|
||||
@@ -240,7 +240,7 @@ async fn get_model_info_rejects_multi_segment_namespace_suffix_matching() {
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
auth_manager,
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
let known_slug = manager
|
||||
@@ -262,8 +262,8 @@ async fn get_model_info_rejects_multi_segment_namespace_suffix_matching() {
|
||||
async fn refresh_available_models_sorts_by_priority() {
|
||||
let server = MockServer::start().await;
|
||||
let remote_models = vec![
|
||||
remote_model("priority-low", "Low", 1),
|
||||
remote_model("priority-high", "High", 0),
|
||||
remote_model("priority-low", "Low", /*priority*/ 1),
|
||||
remote_model("priority-high", "High", /*priority*/ 0),
|
||||
];
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
@@ -313,7 +313,7 @@ async fn refresh_available_models_sorts_by_priority() {
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_uses_cache_when_fresh() {
|
||||
let server = MockServer::start().await;
|
||||
let remote_models = vec![remote_model("cached", "Cached", 5)];
|
||||
let remote_models = vec![remote_model("cached", "Cached", /*priority*/ 5)];
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -354,7 +354,7 @@ async fn refresh_available_models_uses_cache_when_fresh() {
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_refetches_when_cache_stale() {
|
||||
let server = MockServer::start().await;
|
||||
let initial_models = vec![remote_model("stale", "Stale", 1)];
|
||||
let initial_models = vec![remote_model("stale", "Stale", /*priority*/ 1)];
|
||||
let initial_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -387,7 +387,7 @@ async fn refresh_available_models_refetches_when_cache_stale() {
|
||||
.await
|
||||
.expect("cache manipulation succeeds");
|
||||
|
||||
let updated_models = vec![remote_model("fresh", "Fresh", 9)];
|
||||
let updated_models = vec![remote_model("fresh", "Fresh", /*priority*/ 9)];
|
||||
server.reset().await;
|
||||
let refreshed_mock = mount_models_once(
|
||||
&server,
|
||||
@@ -417,7 +417,7 @@ async fn refresh_available_models_refetches_when_cache_stale() {
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_refetches_when_version_mismatch() {
|
||||
let server = MockServer::start().await;
|
||||
let initial_models = vec![remote_model("old", "Old", 1)];
|
||||
let initial_models = vec![remote_model("old", "Old", /*priority*/ 1)];
|
||||
let initial_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -450,7 +450,7 @@ async fn refresh_available_models_refetches_when_version_mismatch() {
|
||||
.await
|
||||
.expect("cache mutation succeeds");
|
||||
|
||||
let updated_models = vec![remote_model("new", "New", 2)];
|
||||
let updated_models = vec![remote_model("new", "New", /*priority*/ 2)];
|
||||
server.reset().await;
|
||||
let refreshed_mock = mount_models_once(
|
||||
&server,
|
||||
@@ -480,7 +480,11 @@ async fn refresh_available_models_refetches_when_version_mismatch() {
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_drops_removed_remote_models() {
|
||||
let server = MockServer::start().await;
|
||||
let initial_models = vec![remote_model("remote-old", "Remote Old", 1)];
|
||||
let initial_models = vec![remote_model(
|
||||
"remote-old",
|
||||
"Remote Old",
|
||||
/*priority*/ 1,
|
||||
)];
|
||||
let initial_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -506,7 +510,11 @@ async fn refresh_available_models_drops_removed_remote_models() {
|
||||
.expect("initial refresh succeeds");
|
||||
|
||||
server.reset().await;
|
||||
let refreshed_models = vec![remote_model("remote-new", "Remote New", 1)];
|
||||
let refreshed_models = vec![remote_model(
|
||||
"remote-new",
|
||||
"Remote New",
|
||||
/*priority*/ 1,
|
||||
)];
|
||||
let refreshed_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -550,7 +558,7 @@ async fn refresh_available_models_skips_network_without_chatgpt_auth() {
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![remote_model(dynamic_slug, "No Auth", 1)],
|
||||
models: vec![remote_model(dynamic_slug, "No Auth", /*priority*/ 1)],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -558,7 +566,7 @@ async fn refresh_available_models_skips_network_without_chatgpt_auth() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let auth_manager = Arc::new(AuthManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
false,
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
));
|
||||
let provider = provider_for(server.uri());
|
||||
@@ -621,7 +629,7 @@ fn models_request_telemetry_emits_auth_env_feedback_tags_on_failure() {
|
||||
.unwrap(),
|
||||
);
|
||||
telemetry.on_request(
|
||||
1,
|
||||
/*attempt*/ 1,
|
||||
Some(StatusCode::UNAUTHORIZED),
|
||||
Some(&TransportError::Http {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
@@ -695,8 +703,10 @@ fn build_available_models_picks_default_after_hiding_hidden_models() {
|
||||
provider,
|
||||
);
|
||||
|
||||
let hidden_model = remote_model_with_visibility("hidden", "Hidden", 0, "hide");
|
||||
let visible_model = remote_model_with_visibility("visible", "Visible", 1, "list");
|
||||
let hidden_model =
|
||||
remote_model_with_visibility("hidden", "Hidden", /*priority*/ 0, "hide");
|
||||
let visible_model =
|
||||
remote_model_with_visibility("visible", "Visible", /*priority*/ 1, "list");
|
||||
|
||||
let expected_hidden = ModelPreset::from(hidden_model.clone());
|
||||
let mut expected_visible = ModelPreset::from(visible_model.clone());
|
||||
|
||||
@@ -120,7 +120,7 @@ fn execpolicy_network_rules_overlay_network_lists() {
|
||||
"blocked.example.com",
|
||||
NetworkRuleProtocol::Https,
|
||||
Decision::Allow,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)
|
||||
.expect("allow rule should be valid");
|
||||
exec_policy
|
||||
@@ -128,7 +128,7 @@ fn execpolicy_network_rules_overlay_network_lists() {
|
||||
"api.example.com",
|
||||
NetworkRuleProtocol::Http,
|
||||
Decision::Forbidden,
|
||||
None,
|
||||
/*justification*/ None,
|
||||
)
|
||||
.expect("deny rule should be valid");
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
Some(ImageDetail::Original)
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_output_image_detail(&features, &model_info, None),
|
||||
normalize_output_image_detail(&features, &model_info, /*detail*/ None),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,10 @@ fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
}"#,
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins(),
|
||||
@@ -340,7 +343,10 @@ fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
|
||||
"---\nname: sample-search\ndescription: search sample data\n---\n",
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].manifest_description.as_deref(),
|
||||
@@ -375,7 +381,10 @@ fn capability_summary_truncates_overlong_plugin_descriptions() {
|
||||
"---\nname: sample-search\ndescription: search sample data\n---\n",
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].manifest_description.as_deref(),
|
||||
@@ -455,7 +464,10 @@ fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
}"#,
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].skill_roots,
|
||||
@@ -562,7 +574,10 @@ fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
}"#,
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(true, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].skill_roots,
|
||||
@@ -622,7 +637,12 @@ fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
|
||||
}"#,
|
||||
);
|
||||
|
||||
let outcome = load_plugins_from_config(&plugin_config_toml(false, true), codex_home.path());
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(
|
||||
/*enabled*/ false, /*plugins_feature_enabled*/ true,
|
||||
),
|
||||
codex_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins(),
|
||||
@@ -831,7 +851,9 @@ fn load_plugins_returns_empty_when_feature_disabled() {
|
||||
);
|
||||
write_file(
|
||||
&codex_home.path().join(CONFIG_TOML_FILE),
|
||||
&plugin_config_toml(true, false),
|
||||
&plugin_config_toml(
|
||||
/*enabled*/ true, /*plugins_feature_enabled*/ false,
|
||||
),
|
||||
);
|
||||
|
||||
let config = load_config_blocking(codex_home.path(), codex_home.path());
|
||||
@@ -1326,7 +1348,7 @@ plugins = false
|
||||
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let outcome = PluginsManager::new(tmp.path().to_path_buf())
|
||||
.sync_plugins_from_remote(&config, None, /*additive_only*/ false)
|
||||
.sync_plugins_from_remote(&config, /*auth*/ None, /*additive_only*/ false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2110,10 +2132,13 @@ plugins = true
|
||||
|
||||
let mut config = load_config(tmp.path(), tmp.path()).await;
|
||||
config.chatgpt_base_url = format!("{}/backend-api/", server.uri());
|
||||
let manager = PluginsManager::new_with_restriction_product(tmp.path().to_path_buf(), None);
|
||||
let manager = PluginsManager::new_with_restriction_product(
|
||||
tmp.path().to_path_buf(),
|
||||
/*restriction_product*/ None,
|
||||
);
|
||||
|
||||
let featured_plugin_ids = manager
|
||||
.featured_plugin_ids_for_config(&config, None)
|
||||
.featured_plugin_ids_for_config(&config, /*auth*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2219,7 +2244,7 @@ fn load_plugins_ignores_project_config_files() {
|
||||
);
|
||||
write_file(
|
||||
&project_root.join(".codex/config.toml"),
|
||||
&plugin_config_toml(true, true),
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
);
|
||||
|
||||
let stack = ConfigLayerStack::new(
|
||||
@@ -2227,7 +2252,10 @@ fn load_plugins_ignores_project_config_files() {
|
||||
ConfigLayerSource::Project {
|
||||
dot_codex_folder: AbsolutePathBuf::try_from(project_root.join(".codex")).unwrap(),
|
||||
},
|
||||
toml::from_str(&plugin_config_toml(true, true)).expect("project config should parse"),
|
||||
toml::from_str(&plugin_config_toml(
|
||||
/*enabled*/ true, /*plugins_feature_enabled*/ true,
|
||||
))
|
||||
.expect("project config should parse"),
|
||||
)],
|
||||
ConfigRequirements::default(),
|
||||
ConfigRequirementsToml::default(),
|
||||
|
||||
@@ -342,7 +342,7 @@ fn list_marketplaces_dedupes_multiple_roots_in_same_repo() {
|
||||
AbsolutePathBuf::try_from(repo_root.clone()).unwrap(),
|
||||
AbsolutePathBuf::try_from(nested_root).unwrap(),
|
||||
],
|
||||
None,
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
@@ -397,10 +397,12 @@ fn list_marketplaces_reads_marketplace_display_name() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let marketplaces =
|
||||
list_marketplaces_with_home(&[AbsolutePathBuf::try_from(repo_root).unwrap()], None)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
let marketplaces = list_marketplaces_with_home(
|
||||
&[AbsolutePathBuf::try_from(repo_root).unwrap()],
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert_eq!(
|
||||
marketplaces[0].interface,
|
||||
@@ -458,7 +460,7 @@ fn list_marketplaces_skips_marketplaces_that_fail_to_load() {
|
||||
AbsolutePathBuf::try_from(valid_repo_root).unwrap(),
|
||||
AbsolutePathBuf::try_from(invalid_repo_root).unwrap(),
|
||||
],
|
||||
None,
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
@@ -503,7 +505,7 @@ fn list_marketplaces_reports_marketplace_load_errors() {
|
||||
AbsolutePathBuf::try_from(valid_repo_root).unwrap(),
|
||||
AbsolutePathBuf::try_from(invalid_repo_root).unwrap(),
|
||||
],
|
||||
None,
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -566,10 +568,12 @@ fn list_marketplaces_resolves_plugin_interface_paths_to_absolute() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let marketplaces =
|
||||
list_marketplaces_with_home(&[AbsolutePathBuf::try_from(repo_root).unwrap()], None)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
let marketplaces = list_marketplaces_with_home(
|
||||
&[AbsolutePathBuf::try_from(repo_root).unwrap()],
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert_eq!(
|
||||
marketplaces[0].plugins[0].policy.installation,
|
||||
@@ -634,10 +638,12 @@ fn list_marketplaces_ignores_legacy_top_level_policy_fields() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let marketplaces =
|
||||
list_marketplaces_with_home(&[AbsolutePathBuf::try_from(repo_root).unwrap()], None)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
let marketplaces = list_marketplaces_with_home(
|
||||
&[AbsolutePathBuf::try_from(repo_root).unwrap()],
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert_eq!(
|
||||
marketplaces[0].plugins[0].policy.installation,
|
||||
@@ -690,10 +696,12 @@ fn list_marketplaces_ignores_plugin_interface_assets_without_dot_slash() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let marketplaces =
|
||||
list_marketplaces_with_home(&[AbsolutePathBuf::try_from(repo_root).unwrap()], None)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
let marketplaces = list_marketplaces_with_home(
|
||||
&[AbsolutePathBuf::try_from(repo_root).unwrap()],
|
||||
/*home_dir*/ None,
|
||||
)
|
||||
.unwrap()
|
||||
.marketplaces;
|
||||
|
||||
assert_eq!(
|
||||
marketplaces[0].plugins[0].interface,
|
||||
|
||||
@@ -75,7 +75,9 @@ async fn make_config_with_project_root_markers(
|
||||
async fn no_doc_file_returns_none() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, 4096, None).await).await;
|
||||
let res =
|
||||
get_user_instructions(&make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await)
|
||||
.await;
|
||||
assert!(
|
||||
res.is_none(),
|
||||
"Expected None when AGENTS.md is absent and no system instructions provided"
|
||||
@@ -89,9 +91,10 @@ async fn doc_smaller_than_limit_is_returned() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("AGENTS.md"), "hello world").unwrap();
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, 4096, None).await)
|
||||
.await
|
||||
.expect("doc expected");
|
||||
let res =
|
||||
get_user_instructions(&make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await)
|
||||
.await
|
||||
.expect("doc expected");
|
||||
|
||||
assert_eq!(
|
||||
res, "hello world",
|
||||
@@ -108,7 +111,7 @@ async fn doc_larger_than_limit_is_truncated() {
|
||||
let huge = "A".repeat(LIMIT * 2); // 2 KiB
|
||||
fs::write(tmp.path().join("AGENTS.md"), &huge).unwrap();
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, LIMIT, None).await)
|
||||
let res = get_user_instructions(&make_config(&tmp, LIMIT, /*instructions*/ None).await)
|
||||
.await
|
||||
.expect("doc expected");
|
||||
|
||||
@@ -137,7 +140,7 @@ async fn finds_doc_in_repo_root() {
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
|
||||
// Build config pointing at the nested dir.
|
||||
let mut cfg = make_config(&repo, 4096, None).await;
|
||||
let mut cfg = make_config(&repo, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let res = get_user_instructions(&cfg).await.expect("doc expected");
|
||||
@@ -150,7 +153,8 @@ async fn zero_byte_limit_disables_docs() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("AGENTS.md"), "something").unwrap();
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, 0, None).await).await;
|
||||
let res =
|
||||
get_user_instructions(&make_config(&tmp, /*limit*/ 0, /*instructions*/ None).await).await;
|
||||
assert!(
|
||||
res.is_none(),
|
||||
"With limit 0 the function should return None"
|
||||
@@ -160,7 +164,7 @@ async fn zero_byte_limit_disables_docs() {
|
||||
#[tokio::test]
|
||||
async fn js_repl_instructions_are_appended_when_enabled() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let mut cfg = make_config(&tmp, 4096, None).await;
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
cfg.features
|
||||
.enable(Feature::JsRepl)
|
||||
.expect("test config should allow js_repl");
|
||||
@@ -175,7 +179,7 @@ async fn js_repl_instructions_are_appended_when_enabled() {
|
||||
#[tokio::test]
|
||||
async fn js_repl_tools_only_instructions_are_feature_gated() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let mut cfg = make_config(&tmp, 4096, None).await;
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
let mut features = cfg.features.get().clone();
|
||||
features
|
||||
.enable(Feature::JsRepl)
|
||||
@@ -194,7 +198,7 @@ async fn js_repl_tools_only_instructions_are_feature_gated() {
|
||||
#[tokio::test]
|
||||
async fn js_repl_image_detail_original_does_not_change_instructions() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let mut cfg = make_config(&tmp, 4096, None).await;
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
let mut features = cfg.features.get().clone();
|
||||
features
|
||||
.enable(Feature::JsRepl)
|
||||
@@ -219,7 +223,7 @@ async fn merges_existing_instructions_with_project_doc() {
|
||||
|
||||
const INSTRUCTIONS: &str = "base instructions";
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)).await)
|
||||
let res = get_user_instructions(&make_config(&tmp, /*limit*/ 4096, Some(INSTRUCTIONS)).await)
|
||||
.await
|
||||
.expect("should produce a combined instruction string");
|
||||
|
||||
@@ -236,7 +240,8 @@ async fn keeps_existing_instructions_when_doc_missing() {
|
||||
|
||||
const INSTRUCTIONS: &str = "some instructions";
|
||||
|
||||
let res = get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)).await).await;
|
||||
let res =
|
||||
get_user_instructions(&make_config(&tmp, /*limit*/ 4096, Some(INSTRUCTIONS)).await).await;
|
||||
|
||||
assert_eq!(res, Some(INSTRUCTIONS.to_string()));
|
||||
}
|
||||
@@ -262,7 +267,7 @@ async fn concatenates_root_and_cwd_docs() {
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
fs::write(nested.join("AGENTS.md"), "crate doc").unwrap();
|
||||
|
||||
let mut cfg = make_config(&repo, 4096, None).await;
|
||||
let mut cfg = make_config(&repo, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let res = get_user_instructions(&cfg).await.expect("doc expected");
|
||||
@@ -279,7 +284,13 @@ async fn project_root_markers_are_honored_for_agents_discovery() {
|
||||
fs::create_dir_all(nested.join(".git")).unwrap();
|
||||
fs::write(nested.join("AGENTS.md"), "child doc").unwrap();
|
||||
|
||||
let mut cfg = make_config_with_project_root_markers(&root, 4096, None, &[".codex-root"]).await;
|
||||
let mut cfg = make_config_with_project_root_markers(
|
||||
&root,
|
||||
/*limit*/ 4096,
|
||||
/*instructions*/ None,
|
||||
&[".codex-root"],
|
||||
)
|
||||
.await;
|
||||
cfg.cwd = nested.abs();
|
||||
|
||||
let discovery = discover_project_doc_paths(&cfg).expect("discover paths");
|
||||
@@ -302,7 +313,7 @@ async fn agents_local_md_preferred() {
|
||||
fs::write(tmp.path().join(DEFAULT_PROJECT_DOC_FILENAME), "versioned").unwrap();
|
||||
fs::write(tmp.path().join(LOCAL_PROJECT_DOC_FILENAME), "local").unwrap();
|
||||
|
||||
let cfg = make_config(&tmp, 4096, None).await;
|
||||
let cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
|
||||
let res = get_user_instructions(&cfg)
|
||||
.await
|
||||
@@ -324,7 +335,13 @@ async fn uses_configured_fallback_when_agents_missing() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("EXAMPLE.md"), "example instructions").unwrap();
|
||||
|
||||
let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md"]).await;
|
||||
let cfg = make_config_with_fallback(
|
||||
&tmp,
|
||||
/*limit*/ 4096,
|
||||
/*instructions*/ None,
|
||||
&["EXAMPLE.md"],
|
||||
)
|
||||
.await;
|
||||
|
||||
let res = get_user_instructions(&cfg)
|
||||
.await
|
||||
@@ -340,7 +357,13 @@ async fn agents_md_preferred_over_fallbacks() {
|
||||
fs::write(tmp.path().join("AGENTS.md"), "primary").unwrap();
|
||||
fs::write(tmp.path().join("EXAMPLE.md"), "secondary").unwrap();
|
||||
|
||||
let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md", ".example.md"]).await;
|
||||
let cfg = make_config_with_fallback(
|
||||
&tmp,
|
||||
/*limit*/ 4096,
|
||||
/*instructions*/ None,
|
||||
&["EXAMPLE.md", ".example.md"],
|
||||
)
|
||||
.await;
|
||||
|
||||
let res = get_user_instructions(&cfg)
|
||||
.await
|
||||
@@ -364,7 +387,7 @@ async fn skills_are_not_appended_to_project_doc() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("AGENTS.md"), "base doc").unwrap();
|
||||
|
||||
let cfg = make_config(&tmp, 4096, None).await;
|
||||
let cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
create_skill(
|
||||
cfg.codex_home.clone(),
|
||||
"pdf-processing",
|
||||
@@ -380,7 +403,7 @@ async fn skills_are_not_appended_to_project_doc() {
|
||||
#[tokio::test]
|
||||
async fn apps_feature_does_not_emit_user_instructions_by_itself() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
let mut cfg = make_config(&tmp, 4096, None).await;
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
cfg.features
|
||||
.enable(Feature::Apps)
|
||||
.expect("test config should allow apps");
|
||||
@@ -394,7 +417,7 @@ async fn apps_feature_does_not_append_to_project_doc_user_instructions() {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
fs::write(tmp.path().join("AGENTS.md"), "base doc").unwrap();
|
||||
|
||||
let mut cfg = make_config(&tmp, 4096, None).await;
|
||||
let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await;
|
||||
cfg.features
|
||||
.enable(Feature::Apps)
|
||||
.expect("test config should allow apps");
|
||||
|
||||
@@ -47,7 +47,7 @@ fn thread_metadata(cwd: &str, title: &str, first_user_message: &str) -> ThreadMe
|
||||
fn workspace_section_requires_meaningful_structure() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
assert_eq!(
|
||||
build_workspace_section_with_user_root(cwd.path(), None),
|
||||
build_workspace_section_with_user_root(cwd.path(), /*user_root*/ None),
|
||||
None
|
||||
);
|
||||
}
|
||||
@@ -58,8 +58,8 @@ fn workspace_section_includes_tree_when_entries_exist() {
|
||||
fs::create_dir(cwd.path().join("docs")).expect("create docs dir");
|
||||
fs::write(cwd.path().join("README.md"), "hello").expect("write readme");
|
||||
|
||||
let section =
|
||||
build_workspace_section_with_user_root(cwd.path(), None).expect("workspace section");
|
||||
let section = build_workspace_section_with_user_root(cwd.path(), /*user_root*/ None)
|
||||
.expect("workspace section");
|
||||
assert!(section.contains("Working directory tree:"));
|
||||
assert!(section.contains("- docs/"));
|
||||
assert!(section.contains("- README.md"));
|
||||
|
||||
@@ -32,7 +32,7 @@ fn default_linux_sandbox_uses_platform_sandbox_tag() {
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
WindowsSandboxLevel::Disabled,
|
||||
);
|
||||
let expected = get_platform_sandbox(false)
|
||||
let expected = get_platform_sandbox(/*windows_sandbox_enabled*/ false)
|
||||
.map(SandboxType::as_metric_tag)
|
||||
.unwrap_or("none");
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -269,9 +269,15 @@ async fn snapshot_shell_does_not_inherit_stdin() -> Result<()> {
|
||||
"HOME=\"{home_display}\"; export HOME; {}",
|
||||
bash_snapshot_script()
|
||||
);
|
||||
let output = run_script_with_timeout(&shell, &script, Duration::from_secs(2), true, home)
|
||||
.await
|
||||
.context("run snapshot command")?;
|
||||
let output = run_script_with_timeout(
|
||||
&shell,
|
||||
&script,
|
||||
Duration::from_secs(2),
|
||||
/*use_login_shell*/ true,
|
||||
home,
|
||||
)
|
||||
.await
|
||||
.context("run snapshot command")?;
|
||||
let read_status = fs::read_to_string(&read_status_path)
|
||||
.await
|
||||
.context("read stdin probe status")?;
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::process::Command;
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn detects_zsh() {
|
||||
let zsh_shell = get_shell(ShellType::Zsh, None).unwrap();
|
||||
let zsh_shell = get_shell(ShellType::Zsh, /*path*/ None).unwrap();
|
||||
|
||||
let shell_path = zsh_shell.shell_path;
|
||||
|
||||
@@ -24,7 +24,7 @@ fn fish_fallback_to_zsh() {
|
||||
|
||||
#[test]
|
||||
fn detects_bash() {
|
||||
let bash_shell = get_shell(ShellType::Bash, None).unwrap();
|
||||
let bash_shell = get_shell(ShellType::Bash, /*path*/ None).unwrap();
|
||||
let shell_path = bash_shell.shell_path;
|
||||
|
||||
assert!(
|
||||
@@ -35,7 +35,7 @@ fn detects_bash() {
|
||||
|
||||
#[test]
|
||||
fn detects_sh() {
|
||||
let sh_shell = get_shell(ShellType::Sh, None).unwrap();
|
||||
let sh_shell = get_shell(ShellType::Sh, /*path*/ None).unwrap();
|
||||
let shell_path = sh_shell.shell_path;
|
||||
assert!(
|
||||
shell_path.file_name().and_then(|name| name.to_str()) == Some("sh"),
|
||||
@@ -48,23 +48,47 @@ fn can_run_on_shell_test() {
|
||||
let cmd = "echo \"Works\"";
|
||||
if cfg!(windows) {
|
||||
assert!(shell_works(
|
||||
get_shell(ShellType::PowerShell, None),
|
||||
get_shell(ShellType::PowerShell, /*path*/ None),
|
||||
"Out-String 'Works'",
|
||||
true,
|
||||
/*required*/ true,
|
||||
));
|
||||
assert!(shell_works(
|
||||
get_shell(ShellType::Cmd, /*path*/ None),
|
||||
cmd,
|
||||
/*required*/ true,
|
||||
));
|
||||
assert!(shell_works(
|
||||
Some(ultimate_fallback_shell()),
|
||||
cmd,
|
||||
/*required*/ true
|
||||
));
|
||||
assert!(shell_works(get_shell(ShellType::Cmd, None), cmd, true,));
|
||||
assert!(shell_works(Some(ultimate_fallback_shell()), cmd, true));
|
||||
} else {
|
||||
assert!(shell_works(Some(ultimate_fallback_shell()), cmd, true));
|
||||
assert!(shell_works(get_shell(ShellType::Zsh, None), cmd, false));
|
||||
assert!(shell_works(get_shell(ShellType::Bash, None), cmd, true));
|
||||
assert!(shell_works(get_shell(ShellType::Sh, None), cmd, true));
|
||||
assert!(shell_works(
|
||||
Some(ultimate_fallback_shell()),
|
||||
cmd,
|
||||
/*required*/ true
|
||||
));
|
||||
assert!(shell_works(
|
||||
get_shell(ShellType::Zsh, /*path*/ None),
|
||||
cmd,
|
||||
/*required*/ false
|
||||
));
|
||||
assert!(shell_works(
|
||||
get_shell(ShellType::Bash, /*path*/ None),
|
||||
cmd,
|
||||
/*required*/ true
|
||||
));
|
||||
assert!(shell_works(
|
||||
get_shell(ShellType::Sh, /*path*/ None),
|
||||
cmd,
|
||||
/*required*/ true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn shell_works(shell: Option<Shell>, command: &str, required: bool) -> bool {
|
||||
if let Some(shell) = shell {
|
||||
let args = shell.derive_exec_args(command, false);
|
||||
let args = shell.derive_exec_args(command, /*use_login_shell*/ false);
|
||||
let output = Command::new(args[0].clone())
|
||||
.args(&args[1..])
|
||||
.output()
|
||||
@@ -85,11 +109,11 @@ fn derive_exec_args() {
|
||||
shell_snapshot: empty_shell_snapshot_receiver(),
|
||||
};
|
||||
assert_eq!(
|
||||
test_bash_shell.derive_exec_args("echo hello", false),
|
||||
test_bash_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
||||
vec!["/bin/bash", "-c", "echo hello"]
|
||||
);
|
||||
assert_eq!(
|
||||
test_bash_shell.derive_exec_args("echo hello", true),
|
||||
test_bash_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
||||
vec!["/bin/bash", "-lc", "echo hello"]
|
||||
);
|
||||
|
||||
@@ -99,11 +123,11 @@ fn derive_exec_args() {
|
||||
shell_snapshot: empty_shell_snapshot_receiver(),
|
||||
};
|
||||
assert_eq!(
|
||||
test_zsh_shell.derive_exec_args("echo hello", false),
|
||||
test_zsh_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
||||
vec!["/bin/zsh", "-c", "echo hello"]
|
||||
);
|
||||
assert_eq!(
|
||||
test_zsh_shell.derive_exec_args("echo hello", true),
|
||||
test_zsh_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
||||
vec!["/bin/zsh", "-lc", "echo hello"]
|
||||
);
|
||||
|
||||
@@ -113,11 +137,11 @@ fn derive_exec_args() {
|
||||
shell_snapshot: empty_shell_snapshot_receiver(),
|
||||
};
|
||||
assert_eq!(
|
||||
test_powershell_shell.derive_exec_args("echo hello", false),
|
||||
test_powershell_shell.derive_exec_args("echo hello", /*use_login_shell*/ false),
|
||||
vec!["pwsh.exe", "-NoProfile", "-Command", "echo hello"]
|
||||
);
|
||||
assert_eq!(
|
||||
test_powershell_shell.derive_exec_args("echo hello", true),
|
||||
test_powershell_shell.derive_exec_args("echo hello", /*use_login_shell*/ true),
|
||||
vec!["pwsh.exe", "-Command", "echo hello"]
|
||||
);
|
||||
}
|
||||
@@ -161,7 +185,7 @@ fn finds_powershell() {
|
||||
return;
|
||||
}
|
||||
|
||||
let powershell_shell = get_shell(ShellType::PowerShell, None).unwrap();
|
||||
let powershell_shell = get_shell(ShellType::PowerShell, /*path*/ None).unwrap();
|
||||
let shell_path = powershell_shell.shell_path;
|
||||
|
||||
assert!(shell_path.ends_with("pwsh.exe") || shell_path.ends_with("powershell.exe"));
|
||||
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
let mut rx = skills_watcher.subscribe();
|
||||
let _registration = skills_watcher
|
||||
.subscriber
|
||||
.register_path(PathBuf::from("/tmp/skill"), true);
|
||||
.register_path(PathBuf::from("/tmp/skill"), /*recursive*/ true);
|
||||
|
||||
file_watcher
|
||||
.send_paths_for_test(vec![PathBuf::from("/tmp/skill/SKILL.md")])
|
||||
|
||||
@@ -28,9 +28,10 @@ async fn handle_non_tool_response_item_strips_citations_from_assistant_message()
|
||||
"hello<oai-mem-citation><citation_entries>\nMEMORY.md:1-2|note=[x]\n</citation_entries>\n<rollout_ids>\n019cc2ea-1dff-7902-8d40-c8f6e5d83cc4\n</rollout_ids></oai-mem-citation> world",
|
||||
);
|
||||
|
||||
let turn_item = handle_non_tool_response_item(&session, &turn_context, &item, false)
|
||||
.await
|
||||
.expect("assistant message should parse");
|
||||
let turn_item =
|
||||
handle_non_tool_response_item(&session, &turn_context, &item, /*plan_mode*/ false)
|
||||
.await
|
||||
.expect("assistant message should parse");
|
||||
|
||||
let TurnItem::AgentMessage(agent_message) = turn_item else {
|
||||
panic!("expected agent message");
|
||||
@@ -60,7 +61,7 @@ fn last_assistant_message_from_item_strips_citations_and_plan_blocks() {
|
||||
"before<oai-mem-citation>doc1</oai-mem-citation>\n<proposed_plan>\n- x\n</proposed_plan>\nafter",
|
||||
);
|
||||
|
||||
let message = last_assistant_message_from_item(&item, true)
|
||||
let message = last_assistant_message_from_item(&item, /*plan_mode*/ true)
|
||||
.expect("assistant text should remain after stripping");
|
||||
|
||||
assert_eq!(message, "before\nafter");
|
||||
@@ -70,14 +71,20 @@ fn last_assistant_message_from_item_strips_citations_and_plan_blocks() {
|
||||
fn last_assistant_message_from_item_returns_none_for_citation_only_message() {
|
||||
let item = assistant_output_text("<oai-mem-citation>doc1</oai-mem-citation>");
|
||||
|
||||
assert_eq!(last_assistant_message_from_item(&item, false), None);
|
||||
assert_eq!(
|
||||
last_assistant_message_from_item(&item, /*plan_mode*/ false),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_assistant_message_from_item_returns_none_for_plan_only_hidden_message() {
|
||||
let item = assistant_output_text("<proposed_plan>\n- x\n</proposed_plan>");
|
||||
|
||||
assert_eq!(last_assistant_message_from_item(&item, true), None);
|
||||
assert_eq!(
|
||||
last_assistant_message_from_item(&item, /*plan_mode*/ true),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -27,5 +27,8 @@ fn large_untracked_warning_disabled_when_threshold_disabled() {
|
||||
ignored_untracked_files: Vec::new(),
|
||||
};
|
||||
|
||||
assert_eq!(format_large_untracked_warning(None, &report), None);
|
||||
assert_eq!(
|
||||
format_large_untracked_warning(/*ignore_large_untracked_dirs*/ None, &report),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ fn test_session_telemetry() -> SessionTelemetry {
|
||||
ThreadId::new(),
|
||||
"gpt-5.1",
|
||||
"gpt-5.1",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
/*account_email*/ None,
|
||||
/*auth_mode*/ None,
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"tty".to_string(),
|
||||
SessionSource::Cli,
|
||||
)
|
||||
@@ -75,7 +75,11 @@ fn metric_point(resource_metrics: &ResourceMetrics) -> (BTreeMap<String, String>
|
||||
fn emit_turn_network_proxy_metric_records_active_turn() {
|
||||
let session_telemetry = test_session_telemetry();
|
||||
|
||||
emit_turn_network_proxy_metric(&session_telemetry, true, ("tmp_mem_enabled", "true"));
|
||||
emit_turn_network_proxy_metric(
|
||||
&session_telemetry,
|
||||
/*network_proxy_active*/ true,
|
||||
("tmp_mem_enabled", "true"),
|
||||
);
|
||||
|
||||
let snapshot = session_telemetry
|
||||
.snapshot_metrics()
|
||||
@@ -96,7 +100,11 @@ fn emit_turn_network_proxy_metric_records_active_turn() {
|
||||
fn emit_turn_network_proxy_metric_records_inactive_turn() {
|
||||
let session_telemetry = test_session_telemetry();
|
||||
|
||||
emit_turn_network_proxy_metric(&session_telemetry, false, ("tmp_mem_enabled", "false"));
|
||||
emit_turn_network_proxy_metric(
|
||||
&session_telemetry,
|
||||
/*network_proxy_active*/ false,
|
||||
("tmp_mem_enabled", "false"),
|
||||
);
|
||||
|
||||
let snapshot = session_telemetry
|
||||
.snapshot_metrics()
|
||||
|
||||
@@ -75,7 +75,7 @@ fn truncates_before_requested_user_message() {
|
||||
.collect();
|
||||
let truncated = truncate_before_nth_user_message(
|
||||
InitialHistory::Forked(initial),
|
||||
1,
|
||||
/*n*/ 1,
|
||||
&SnapshotTurnState {
|
||||
ends_mid_turn: false,
|
||||
active_turn_id: None,
|
||||
@@ -100,7 +100,7 @@ fn truncates_before_requested_user_message() {
|
||||
.collect();
|
||||
let truncated2 = truncate_before_nth_user_message(
|
||||
InitialHistory::Forked(initial2.clone()),
|
||||
2,
|
||||
/*n*/ 2,
|
||||
&SnapshotTurnState {
|
||||
ends_mid_turn: false,
|
||||
active_turn_id: None,
|
||||
@@ -210,7 +210,7 @@ async fn ignores_session_prefix_messages_when_truncating() {
|
||||
|
||||
let truncated = truncate_before_nth_user_message(
|
||||
InitialHistory::Forked(rollout_items),
|
||||
1,
|
||||
/*n*/ 1,
|
||||
&SnapshotTurnState {
|
||||
ends_mid_turn: false,
|
||||
active_turn_id: None,
|
||||
|
||||
@@ -61,7 +61,8 @@ fn truncates_rollout_from_start_before_nth_user_only() {
|
||||
.map(RolloutItem::ResponseItem)
|
||||
.collect();
|
||||
|
||||
let truncated = truncate_rollout_before_nth_user_message_from_start(&rollout, 1);
|
||||
let truncated =
|
||||
truncate_rollout_before_nth_user_message_from_start(&rollout, /*n_from_start*/ 1);
|
||||
let expected = vec![
|
||||
RolloutItem::ResponseItem(items[0].clone()),
|
||||
RolloutItem::ResponseItem(items[1].clone()),
|
||||
@@ -72,7 +73,8 @@ fn truncates_rollout_from_start_before_nth_user_only() {
|
||||
serde_json::to_value(&expected).unwrap()
|
||||
);
|
||||
|
||||
let truncated2 = truncate_rollout_before_nth_user_message_from_start(&rollout, 2);
|
||||
let truncated2 =
|
||||
truncate_rollout_before_nth_user_message_from_start(&rollout, /*n_from_start*/ 2);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&truncated2).unwrap(),
|
||||
serde_json::to_value(&rollout).unwrap()
|
||||
@@ -113,7 +115,10 @@ fn truncates_rollout_from_start_applies_thread_rollback_markers() {
|
||||
|
||||
// Effective user history after applying rollback(1) is: u1, u3, u4.
|
||||
// So n_from_start=2 should cut before u4 (not u3).
|
||||
let truncated = truncate_rollout_before_nth_user_message_from_start(&rollout_items, 2);
|
||||
let truncated = truncate_rollout_before_nth_user_message_from_start(
|
||||
&rollout_items,
|
||||
/*n_from_start*/ 2,
|
||||
);
|
||||
let expected = rollout_items[..7].to_vec();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&truncated).unwrap(),
|
||||
@@ -136,7 +141,10 @@ async fn ignores_session_prefix_messages_when_truncating_rollout_from_start() {
|
||||
.map(RolloutItem::ResponseItem)
|
||||
.collect();
|
||||
|
||||
let truncated = truncate_rollout_before_nth_user_message_from_start(&rollout_items, 1);
|
||||
let truncated = truncate_rollout_before_nth_user_message_from_start(
|
||||
&rollout_items,
|
||||
/*n_from_start*/ 1,
|
||||
);
|
||||
let expected: Vec<RolloutItem> = vec![
|
||||
RolloutItem::ResponseItem(items[0].clone()),
|
||||
RolloutItem::ResponseItem(items[1].clone()),
|
||||
|
||||
@@ -58,7 +58,7 @@ async fn emit_js_repl_exec_end_sends_event() {
|
||||
turn.as_ref(),
|
||||
"call-1",
|
||||
"hello",
|
||||
None,
|
||||
/*error*/ None,
|
||||
Duration::from_millis(12),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -34,9 +34,11 @@ async fn lists_directory_entries() {
|
||||
symlink(dir_path.join("entry.txt"), &link_path).expect("create symlink");
|
||||
}
|
||||
|
||||
let entries = list_dir_slice(dir_path, 1, 20, 3)
|
||||
.await
|
||||
.expect("list directory");
|
||||
let entries = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 20, /*depth*/ 3,
|
||||
)
|
||||
.await
|
||||
.expect("list directory");
|
||||
|
||||
#[cfg(unix)]
|
||||
let expected = vec![
|
||||
@@ -68,9 +70,11 @@ async fn errors_when_offset_exceeds_entries() {
|
||||
.await
|
||||
.expect("create sub dir");
|
||||
|
||||
let err = list_dir_slice(dir_path, 10, 1, 2)
|
||||
.await
|
||||
.expect_err("offset exceeds entries");
|
||||
let err = list_dir_slice(
|
||||
dir_path, /*offset*/ 10, /*limit*/ 1, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect_err("offset exceeds entries");
|
||||
assert_eq!(
|
||||
err,
|
||||
FunctionCallError::RespondToModel("offset exceeds directory entry count".to_string())
|
||||
@@ -95,17 +99,21 @@ async fn respects_depth_parameter() {
|
||||
.await
|
||||
.expect("write deeper");
|
||||
|
||||
let entries_depth_one = list_dir_slice(dir_path, 1, 10, 1)
|
||||
.await
|
||||
.expect("list depth 1");
|
||||
let entries_depth_one = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 10, /*depth*/ 1,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 1");
|
||||
assert_eq!(
|
||||
entries_depth_one,
|
||||
vec!["nested/".to_string(), "root.txt".to_string(),]
|
||||
);
|
||||
|
||||
let entries_depth_two = list_dir_slice(dir_path, 1, 20, 2)
|
||||
.await
|
||||
.expect("list depth 2");
|
||||
let entries_depth_two = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 20, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 2");
|
||||
assert_eq!(
|
||||
entries_depth_two,
|
||||
vec![
|
||||
@@ -116,9 +124,11 @@ async fn respects_depth_parameter() {
|
||||
]
|
||||
);
|
||||
|
||||
let entries_depth_three = list_dir_slice(dir_path, 1, 30, 3)
|
||||
.await
|
||||
.expect("list depth 3");
|
||||
let entries_depth_three = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 30, /*depth*/ 3,
|
||||
)
|
||||
.await
|
||||
.expect("list depth 3");
|
||||
assert_eq!(
|
||||
entries_depth_three,
|
||||
vec![
|
||||
@@ -148,9 +158,11 @@ async fn paginates_in_sorted_order() {
|
||||
.await
|
||||
.expect("write b child");
|
||||
|
||||
let first_page = list_dir_slice(dir_path, 1, 2, 2)
|
||||
.await
|
||||
.expect("list page one");
|
||||
let first_page = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 2, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list page one");
|
||||
assert_eq!(
|
||||
first_page,
|
||||
vec![
|
||||
@@ -160,9 +172,11 @@ async fn paginates_in_sorted_order() {
|
||||
]
|
||||
);
|
||||
|
||||
let second_page = list_dir_slice(dir_path, 3, 2, 2)
|
||||
.await
|
||||
.expect("list page two");
|
||||
let second_page = list_dir_slice(
|
||||
dir_path, /*offset*/ 3, /*limit*/ 2, /*depth*/ 2,
|
||||
)
|
||||
.await
|
||||
.expect("list page two");
|
||||
assert_eq!(
|
||||
second_page,
|
||||
vec!["b/".to_string(), " b_child.txt".to_string()]
|
||||
@@ -183,7 +197,7 @@ async fn handles_large_limit_without_overflow() {
|
||||
.await
|
||||
.expect("write gamma");
|
||||
|
||||
let entries = list_dir_slice(dir_path, 2, usize::MAX, 1)
|
||||
let entries = list_dir_slice(dir_path, /*offset*/ 2, usize::MAX, /*depth*/ 1)
|
||||
.await
|
||||
.expect("list without overflow");
|
||||
assert_eq!(
|
||||
@@ -204,9 +218,11 @@ async fn indicates_truncated_results() {
|
||||
.expect("write file");
|
||||
}
|
||||
|
||||
let entries = list_dir_slice(dir_path, 1, 25, 1)
|
||||
.await
|
||||
.expect("list directory");
|
||||
let entries = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 25, /*depth*/ 1,
|
||||
)
|
||||
.await
|
||||
.expect("list directory");
|
||||
assert_eq!(entries.len(), 26);
|
||||
assert_eq!(
|
||||
entries.last(),
|
||||
@@ -226,7 +242,10 @@ async fn truncation_respects_sorted_order() -> anyhow::Result<()> {
|
||||
tokio::fs::write(nested.join("child.txt"), b"child").await?;
|
||||
tokio::fs::write(deeper.join("grandchild.txt"), b"deep").await?;
|
||||
|
||||
let entries_depth_three = list_dir_slice(dir_path, 1, 3, 3).await?;
|
||||
let entries_depth_three = list_dir_slice(
|
||||
dir_path, /*offset*/ 1, /*limit*/ 3, /*depth*/ 3,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
entries_depth_three,
|
||||
vec![
|
||||
|
||||
@@ -261,7 +261,7 @@ mod tests {
|
||||
let cwd = tempdir().expect("tempdir");
|
||||
|
||||
let normalized = normalize_and_validate_additional_permissions(
|
||||
false,
|
||||
/*additional_permissions_allowed*/ false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
@@ -271,7 +271,7 @@ mod tests {
|
||||
}),
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
Some(network_permissions()),
|
||||
true,
|
||||
/*permissions_preapproved*/ true,
|
||||
cwd.path(),
|
||||
)
|
||||
.expect("preapproved permissions should be allowed");
|
||||
@@ -284,11 +284,11 @@ mod tests {
|
||||
let cwd = tempdir().expect("tempdir");
|
||||
|
||||
let err = normalize_and_validate_additional_permissions(
|
||||
false,
|
||||
/*additional_permissions_allowed*/ false,
|
||||
AskForApproval::OnRequest,
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
Some(network_permissions()),
|
||||
false,
|
||||
/*permissions_preapproved*/ false,
|
||||
cwd.path(),
|
||||
)
|
||||
.expect_err("fresh inline permission requests should remain disabled");
|
||||
@@ -305,7 +305,7 @@ mod tests {
|
||||
let granted_permissions = file_system_permissions(cwd.path());
|
||||
let implicit_permissions = implicit_granted_permissions(
|
||||
SandboxPermissions::UseDefault,
|
||||
None,
|
||||
/*additional_permissions*/ None,
|
||||
&EffectiveAdditionalPermissions {
|
||||
sandbox_permissions: SandboxPermissions::WithAdditionalPermissions,
|
||||
additional_permissions: Some(granted_permissions.clone()),
|
||||
|
||||
@@ -82,7 +82,7 @@ fn parse_agent_id(id: &str) -> ThreadId {
|
||||
fn thread_manager() -> ThreadManager {
|
||||
ThreadManager::with_models_provider_for_tests(
|
||||
CodexAuth::from_api_key("dummy"),
|
||||
built_in_model_providers(/* openai_base_url */ None)["openai"].clone(),
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -247,7 +247,8 @@ async fn spawn_agent_uses_explorer_role_and_preserves_approval_policy() {
|
||||
let manager = thread_manager();
|
||||
session.services.agent_control = manager.agent_control();
|
||||
let mut config = (*turn.config).clone();
|
||||
let provider = built_in_model_providers(/* openai_base_url */ None)["ollama"].clone();
|
||||
let provider =
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["ollama"].clone();
|
||||
config.model_provider_id = "ollama".to_string();
|
||||
config.model_provider = provider.clone();
|
||||
config
|
||||
@@ -962,7 +963,7 @@ async fn multi_agent_v2_send_message_interrupts_busy_child_without_triggering_tu
|
||||
AgentPath::try_from("/root/worker").expect("agent path"),
|
||||
Vec::new(),
|
||||
"continue".to_string(),
|
||||
false,
|
||||
/*trigger_turn*/ false,
|
||||
),
|
||||
);
|
||||
let saw_user_message = history_items.iter().any(|item| {
|
||||
@@ -1094,7 +1095,7 @@ async fn multi_agent_v2_assign_task_interrupts_busy_child_without_losing_message
|
||||
AgentPath::try_from("/root/worker").expect("agent path"),
|
||||
Vec::new(),
|
||||
"continue".to_string(),
|
||||
true,
|
||||
/*trigger_turn*/ true,
|
||||
),
|
||||
);
|
||||
let saw_user_message = history_items.iter().any(|item| {
|
||||
@@ -1636,8 +1637,8 @@ async fn resume_agent_restores_closed_agent_and_accepts_send_input() {
|
||||
phase: None,
|
||||
})]),
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy")),
|
||||
false,
|
||||
None,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("start thread");
|
||||
@@ -2557,7 +2558,7 @@ async fn build_agent_resume_config_clears_base_instructions() {
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy set");
|
||||
|
||||
let config = build_agent_resume_config(&turn, 0).expect("resume config");
|
||||
let config = build_agent_resume_config(&turn, /*child_depth*/ 0).expect("resume config");
|
||||
|
||||
let mut expected = (*turn.config).clone();
|
||||
expected.base_instructions = None;
|
||||
|
||||
@@ -12,23 +12,38 @@ fn request_user_input_mode_availability_defaults_to_plan_only() {
|
||||
#[test]
|
||||
fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Plan, false),
|
||||
request_user_input_unavailable_message(
|
||||
ModeKind::Plan,
|
||||
/*default_mode_request_user_input*/ false
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Default, false),
|
||||
request_user_input_unavailable_message(
|
||||
ModeKind::Default,
|
||||
/*default_mode_request_user_input*/ false
|
||||
),
|
||||
Some("request_user_input is unavailable in Default mode".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Default, true),
|
||||
request_user_input_unavailable_message(
|
||||
ModeKind::Default,
|
||||
/*default_mode_request_user_input*/ true
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::Execute, false),
|
||||
request_user_input_unavailable_message(
|
||||
ModeKind::Execute,
|
||||
/*default_mode_request_user_input*/ false
|
||||
),
|
||||
Some("request_user_input is unavailable in Execute mode".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_unavailable_message(ModeKind::PairProgramming, false),
|
||||
request_user_input_unavailable_message(
|
||||
ModeKind::PairProgramming,
|
||||
/*default_mode_request_user_input*/ false
|
||||
),
|
||||
Some("request_user_input is unavailable in Pair Programming mode".to_string())
|
||||
);
|
||||
}
|
||||
@@ -36,11 +51,11 @@ fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
|
||||
#[test]
|
||||
fn request_user_input_tool_description_mentions_available_modes() {
|
||||
assert_eq!(
|
||||
request_user_input_tool_description(false),
|
||||
request_user_input_tool_description(/*default_mode_request_user_input*/ false),
|
||||
"Request user input for one to three short questions and wait for the response. This tool is only available in Plan mode.".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
request_user_input_tool_description(true),
|
||||
request_user_input_tool_description(/*default_mode_request_user_input*/ true),
|
||||
"Request user input for one to three short questions and wait for the response. This tool is only available in Default or Plan mode.".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,12 +63,12 @@ fn commands_generated_by_shell_command_handler_can_be_matched_by_is_known_safe_c
|
||||
}
|
||||
|
||||
fn assert_safe(shell: &Shell, command: &str) {
|
||||
assert!(is_known_safe_command(
|
||||
&shell.derive_exec_args(command, /* use_login_shell */ true)
|
||||
));
|
||||
assert!(is_known_safe_command(
|
||||
&shell.derive_exec_args(command, /* use_login_shell */ false)
|
||||
));
|
||||
assert!(is_known_safe_command(&shell.derive_exec_args(
|
||||
command, /* use_login_shell */ /*use_login_shell*/ true
|
||||
)));
|
||||
assert!(is_known_safe_command(&shell.derive_exec_args(
|
||||
command, /* use_login_shell */ /*use_login_shell*/ false
|
||||
)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -82,7 +82,9 @@ async fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_contex
|
||||
let sandbox_permissions = SandboxPermissions::RequireEscalated;
|
||||
let justification = Some("because tests".to_string());
|
||||
|
||||
let expected_command = session.user_shell().derive_exec_args(&command, true);
|
||||
let expected_command = session
|
||||
.user_shell()
|
||||
.derive_exec_args(&command, /*use_login_shell*/ true);
|
||||
let expected_cwd = turn_context.resolve_path(workdir.clone());
|
||||
let expected_env = create_env(
|
||||
&turn_context.shell_environment_policy,
|
||||
@@ -105,7 +107,7 @@ async fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_contex
|
||||
&session,
|
||||
&turn_context,
|
||||
session.conversation_id,
|
||||
true,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.expect("login shells should be allowed");
|
||||
|
||||
@@ -132,17 +134,24 @@ fn shell_command_handler_respects_explicit_login_flag() {
|
||||
shell_snapshot,
|
||||
};
|
||||
|
||||
let login_command = ShellCommandHandler::base_command(&shell, "echo login shell", true);
|
||||
let login_command = ShellCommandHandler::base_command(
|
||||
&shell,
|
||||
"echo login shell",
|
||||
/*use_login_shell*/ true,
|
||||
);
|
||||
assert_eq!(
|
||||
login_command,
|
||||
shell.derive_exec_args("echo login shell", true)
|
||||
shell.derive_exec_args("echo login shell", /*use_login_shell*/ true)
|
||||
);
|
||||
|
||||
let non_login_command =
|
||||
ShellCommandHandler::base_command(&shell, "echo non login shell", false);
|
||||
let non_login_command = ShellCommandHandler::base_command(
|
||||
&shell,
|
||||
"echo non login shell",
|
||||
/*use_login_shell*/ false,
|
||||
);
|
||||
assert_eq!(
|
||||
non_login_command,
|
||||
shell.derive_exec_args("echo non login shell", false)
|
||||
shell.derive_exec_args("echo non login shell", /*use_login_shell*/ false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,20 +174,23 @@ async fn shell_command_handler_defaults_to_non_login_when_disallowed() {
|
||||
&session,
|
||||
&turn_context,
|
||||
session.conversation_id,
|
||||
false,
|
||||
/*allow_login_shell*/ false,
|
||||
)
|
||||
.expect("non-login shells should still be allowed");
|
||||
|
||||
assert_eq!(
|
||||
exec_params.command,
|
||||
session.user_shell().derive_exec_args("echo hello", false)
|
||||
session
|
||||
.user_shell()
|
||||
.derive_exec_args("echo hello", /*use_login_shell*/ false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_command_handler_rejects_login_when_disallowed() {
|
||||
let err = ShellCommandHandler::resolve_use_login_shell(Some(true), false)
|
||||
.expect_err("explicit login should be rejected");
|
||||
let err =
|
||||
ShellCommandHandler::resolve_use_login_shell(Some(true), /*allow_login_shell*/ false)
|
||||
.expect_err("explicit login should be rejected");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
|
||||
@@ -31,7 +31,7 @@ fn test_get_command_uses_default_shell_when_unspecified() -> anyhow::Result<()>
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -52,7 +52,7 @@ fn test_get_command_respects_explicit_bash_shell() -> anyhow::Result<()> {
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -78,7 +78,7 @@ fn test_get_command_respects_explicit_powershell_shell() -> anyhow::Result<()> {
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -98,7 +98,7 @@ fn test_get_command_respects_explicit_cmd_shell() -> anyhow::Result<()> {
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
true,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -115,7 +115,7 @@ fn test_get_command_rejects_explicit_login_when_disallowed() -> anyhow::Result<(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
false,
|
||||
/*allow_login_shell*/ false,
|
||||
)
|
||||
.expect_err("explicit login should be rejected");
|
||||
|
||||
@@ -144,8 +144,13 @@ fn test_get_command_ignores_explicit_shell_in_zsh_fork_mode() -> anyhow::Result<
|
||||
})?,
|
||||
});
|
||||
|
||||
let command = get_command(&args, Arc::new(default_user_shell()), &shell_mode, true)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(default_user_shell()),
|
||||
&shell_mode,
|
||||
/*allow_login_shell*/ true,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
|
||||
@@ -47,13 +47,19 @@ fn node_version_parses_v_prefix_and_suffix() {
|
||||
#[test]
|
||||
fn truncate_utf8_prefix_by_bytes_preserves_character_boundaries() {
|
||||
let input = "aé🙂z";
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 0), "");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 1), "a");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 2), "a");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 3), "aé");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 6), "aé");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 7), "aé🙂");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, 8), "aé🙂z");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 0), "");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 1), "a");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 2), "a");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 3), "aé");
|
||||
assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 6), "aé");
|
||||
assert_eq!(
|
||||
truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 7),
|
||||
"aé🙂"
|
||||
);
|
||||
assert_eq!(
|
||||
truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 8),
|
||||
"aé🙂z"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -203,7 +209,7 @@ async fn wait_for_exec_tool_calls_map_drains_inflight_calls_without_hanging() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reset_waits_for_exec_lock_before_clearing_exec_tool_calls() {
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let permit = manager
|
||||
@@ -300,8 +306,11 @@ async fn emitted_image_content_item_does_not_force_original_when_enabled() {
|
||||
.expect("test turn features should allow feature update");
|
||||
turn.model_info.supports_image_detail_original = true;
|
||||
|
||||
let content_item =
|
||||
emitted_image_content_item(&turn, "data:image/png;base64,AAA".to_string(), None);
|
||||
let content_item = emitted_image_content_item(
|
||||
&turn,
|
||||
"data:image/png;base64,AAA".to_string(),
|
||||
/*detail*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
content_item,
|
||||
@@ -427,7 +436,7 @@ fn summarize_tool_call_error_marks_error_payload() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reset_clears_inflight_exec_tool_calls_without_waiting() {
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let exec_id = Uuid::new_v4().to_string();
|
||||
@@ -460,7 +469,7 @@ async fn reset_clears_inflight_exec_tool_calls_without_waiting() {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn reset_aborts_inflight_exec_tool_tasks() {
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let exec_id = Uuid::new_v4().to_string();
|
||||
@@ -621,14 +630,14 @@ async fn interrupt_turn_exec_clears_matching_submitted_exec() -> anyhow::Result<
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let (_session, turn) = make_session_and_context().await;
|
||||
let turn = Arc::new(turn);
|
||||
let dependency_env = HashMap::new();
|
||||
let mut state = manager
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, None)
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let child = Arc::clone(&state.child);
|
||||
@@ -667,14 +676,14 @@ async fn interrupt_turn_exec_resets_matching_pending_kernel_start() -> anyhow::R
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let (_session, turn) = make_session_and_context().await;
|
||||
let turn = Arc::new(turn);
|
||||
let dependency_env = HashMap::new();
|
||||
let mut state = manager
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, None)
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
state.top_level_exec_state = TopLevelExecState::FreshKernel {
|
||||
@@ -711,14 +720,14 @@ async fn interrupt_turn_exec_does_not_reset_reused_kernel_before_submit() -> any
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let manager = JsReplManager::new(None, Vec::new())
|
||||
let manager = JsReplManager::new(/*node_path*/ None, Vec::new())
|
||||
.await
|
||||
.expect("manager should initialize");
|
||||
let (_session, turn) = make_session_and_context().await;
|
||||
let turn = Arc::new(turn);
|
||||
let dependency_env = HashMap::new();
|
||||
let mut state = manager
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, None)
|
||||
.start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
state.top_level_exec_state = TopLevelExecState::ReusedKernelPending {
|
||||
|
||||
@@ -29,7 +29,7 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
|
||||
(namespaced_name, Arc::clone(&namespaced_handler)),
|
||||
]));
|
||||
|
||||
let plain = registry.handler(tool_name, None);
|
||||
let plain = registry.handler(tool_name, /*namespace*/ None);
|
||||
let namespaced = registry.handler(tool_name, Some(namespace));
|
||||
let missing_namespaced = registry.handler(tool_name, Some("mcp__codex_apps__calendar"));
|
||||
|
||||
|
||||
@@ -126,8 +126,8 @@ fn build_sandbox_command_falls_back_to_current_exe_for_apply_patch() {
|
||||
timeout_ms: None,
|
||||
};
|
||||
|
||||
let command =
|
||||
ApplyPatchRuntime::build_sandbox_command(&request, None).expect("build sandbox command");
|
||||
let command = ApplyPatchRuntime::build_sandbox_command(&request, /*codex_self_exe*/ None)
|
||||
.expect("build sandbox command");
|
||||
|
||||
assert_eq!(
|
||||
command.program,
|
||||
|
||||
@@ -95,15 +95,24 @@ fn execve_prompt_rejection_keeps_unmatched_commands_on_sandbox_flag() {
|
||||
#[test]
|
||||
fn approval_sandbox_permissions_only_downgrades_preapproved_additional_permissions() {
|
||||
assert_eq!(
|
||||
super::approval_sandbox_permissions(SandboxPermissions::WithAdditionalPermissions, true),
|
||||
super::approval_sandbox_permissions(
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
/*additional_permissions_preapproved*/ true
|
||||
),
|
||||
SandboxPermissions::UseDefault,
|
||||
);
|
||||
assert_eq!(
|
||||
super::approval_sandbox_permissions(SandboxPermissions::WithAdditionalPermissions, false),
|
||||
super::approval_sandbox_permissions(
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
/*additional_permissions_preapproved*/ false
|
||||
),
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
);
|
||||
assert_eq!(
|
||||
super::approval_sandbox_permissions(SandboxPermissions::RequireEscalated, true),
|
||||
super::approval_sandbox_permissions(
|
||||
SandboxPermissions::RequireEscalated,
|
||||
/*additional_permissions_preapproved*/ true
|
||||
),
|
||||
SandboxPermissions::RequireEscalated,
|
||||
);
|
||||
}
|
||||
@@ -278,7 +287,7 @@ fn shell_request_escalation_execution_is_explicit() {
|
||||
&sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
None,
|
||||
/*additional_permissions*/ None,
|
||||
),
|
||||
EscalationExecution::TurnDefault,
|
||||
);
|
||||
@@ -288,7 +297,7 @@ fn shell_request_escalation_execution_is_explicit() {
|
||||
&sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
None,
|
||||
/*additional_permissions*/ None,
|
||||
),
|
||||
EscalationExecution::Unsandboxed,
|
||||
);
|
||||
@@ -466,7 +475,7 @@ fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default(
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
sandbox_permissions: super::approval_sandbox_permissions(
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
true,
|
||||
/*additional_permissions_preapproved*/ true,
|
||||
),
|
||||
enable_shell_wrapper_parsing: false,
|
||||
},
|
||||
|
||||
@@ -2389,7 +2389,13 @@ pub(crate) fn build_specs(
|
||||
app_tools: Option<HashMap<String, ToolInfo>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolRegistryBuilder {
|
||||
build_specs_with_discoverable_tools(config, mcp_tools, app_tools, None, dynamic_tools)
|
||||
build_specs_with_discoverable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
app_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn build_specs_with_discoverable_tools(
|
||||
|
||||
@@ -220,22 +220,22 @@ fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
#[test]
|
||||
fn unified_exec_is_blocked_for_windows_sandboxed_policies_only() {
|
||||
assert!(!unified_exec_allowed_in_environment(
|
||||
true,
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(!unified_exec_allowed_in_environment(
|
||||
true,
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::new_workspace_write_policy(),
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
true,
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
));
|
||||
assert!(unified_exec_allowed_in_environment(
|
||||
true,
|
||||
/*is_windows*/ true,
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
));
|
||||
@@ -280,7 +280,13 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
// Build actual map name -> spec
|
||||
use std::collections::BTreeMap;
|
||||
@@ -301,7 +307,9 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
// Build expected from the same helpers used by the builder.
|
||||
let mut expected: BTreeMap<String, ToolSpec> = BTreeMap::from([]);
|
||||
for spec in [
|
||||
create_exec_command_tool(true, false),
|
||||
create_exec_command_tool(
|
||||
/*allow_login_shell*/ true, /*exec_permission_approvals_enabled*/ false,
|
||||
),
|
||||
create_write_stdin_tool(),
|
||||
PLAN_TOOL.clone(),
|
||||
create_request_user_input_tool(CollaborationModesConfig::default()),
|
||||
@@ -376,7 +384,13 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&["spawn_agent", "send_input", "wait_agent", "close_agent"],
|
||||
@@ -402,7 +416,13 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
@@ -554,7 +574,13 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
@@ -584,7 +610,13 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &view_image.spec else {
|
||||
panic!("view_image should be a function tool");
|
||||
@@ -613,7 +645,13 @@ fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &view_image.spec else {
|
||||
panic!("view_image should be a function tool");
|
||||
@@ -652,7 +690,13 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
&[
|
||||
@@ -683,7 +727,13 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let request_user_input_tool = find_tool(&tools, "request_user_input");
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
@@ -701,7 +751,13 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let request_user_input_tool = find_tool(&tools, "request_user_input");
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
@@ -726,7 +782,13 @@ fn request_permissions_requires_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
|
||||
let mut features = Features::with_defaults();
|
||||
@@ -741,7 +803,13 @@ fn request_permissions_requires_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let request_permissions_tool = find_tool(&tools, "request_permissions");
|
||||
assert_eq!(
|
||||
request_permissions_tool.spec,
|
||||
@@ -765,7 +833,13 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
}
|
||||
@@ -786,7 +860,13 @@ fn get_memory_requires_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert!(
|
||||
!tools.iter().any(|t| t.spec.name() == "get_memory"),
|
||||
"get_memory should be disabled when memory_tool feature is off"
|
||||
@@ -809,7 +889,13 @@ fn js_repl_requires_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert!(
|
||||
!tools.iter().any(|tool| tool.spec.name() == "js_repl"),
|
||||
@@ -838,7 +924,13 @@ fn js_repl_enabled_adds_tools() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(&tools, &["js_repl", "js_repl_reset"]);
|
||||
}
|
||||
|
||||
@@ -864,7 +956,13 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (default_tools, _) = build_specs(&default_tools_config, None, None, &[]).build();
|
||||
let (default_tools, _) = build_specs(
|
||||
&default_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert!(
|
||||
!default_tools
|
||||
.iter()
|
||||
@@ -881,7 +979,13 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (supported_tools, _) = build_specs(&supported_tools_config, None, None, &[]).build();
|
||||
let (supported_tools, _) = build_specs(
|
||||
&supported_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_contains_tool_names(&supported_tools, &["image_generation"]);
|
||||
let image_generation_tool = find_tool(&supported_tools, "image_generation");
|
||||
assert_eq!(
|
||||
@@ -901,7 +1005,13 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert!(
|
||||
!tools
|
||||
.iter()
|
||||
@@ -992,7 +1102,13 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -1023,7 +1139,13 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -1068,7 +1190,13 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_web_search_config(Some(web_search_config.clone()));
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -1105,7 +1233,13 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -1140,7 +1274,13 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert!(
|
||||
!tools.iter().any(|tool| matches!(
|
||||
@@ -1166,7 +1306,13 @@ fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, Some(HashMap::new()), None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::new()),
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -1406,7 +1552,13 @@ fn test_build_specs_default_shell_present() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, Some(HashMap::new()), None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::new()),
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
// Only check the shell variant and a couple of core tools.
|
||||
let mut subset = vec!["exec_command", "write_stdin", "update_plan"];
|
||||
@@ -1496,7 +1648,13 @@ fn test_parallel_support_flags() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert!(find_tool(&tools, "exec_command").supports_parallel_tool_calls);
|
||||
assert!(!find_tool(&tools, "write_stdin").supports_parallel_tool_calls);
|
||||
@@ -1518,7 +1676,13 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
assert!(
|
||||
tools
|
||||
@@ -1568,7 +1732,7 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1653,7 +1817,7 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
),
|
||||
]);
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, Some(tools_map), None, &[]).build();
|
||||
let (tools, _) = build_specs(&tools_config, Some(tools_map), /*app_tools*/ None, &[]).build();
|
||||
|
||||
// Only assert that the MCP tools themselves are sorted by fully-qualified name.
|
||||
let mcp_names: Vec<_> = tools
|
||||
@@ -1827,7 +1991,13 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, app_tools.clone(), &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
app_tools.clone(),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
|
||||
let available_models = Vec::new();
|
||||
@@ -1840,7 +2010,13 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, app_tools.clone(), &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
app_tools.clone(),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
|
||||
let mut features = Features::with_defaults();
|
||||
@@ -1855,7 +2031,7 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, None, app_tools, &[]).build();
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, app_tools, &[]).build();
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
|
||||
}
|
||||
|
||||
@@ -1879,8 +2055,8 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
None,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1919,8 +2095,8 @@ fn tool_suggest_can_be_registered_without_search_tool() {
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
None,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1974,8 +2150,8 @@ fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
});
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
None,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
discoverable_tools.clone(),
|
||||
&[],
|
||||
)
|
||||
@@ -2007,7 +2183,13 @@ fn search_tool_description_handles_no_enabled_apps() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, None, Some(HashMap::new()), &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
Some(HashMap::new()),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
|
||||
panic!("expected tool_search tool");
|
||||
@@ -2036,7 +2218,7 @@ fn search_tool_description_falls_back_to_connector_name_without_description() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
Some(HashMap::from([(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
@@ -2085,7 +2267,7 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
|
||||
let (_, registry) = build_specs(
|
||||
&tools_config,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
Some(HashMap::from([
|
||||
(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
@@ -2128,8 +2310,8 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
|
||||
let alias = tool_handler_key("_create_event", Some("mcp__codex_apps__calendar"));
|
||||
|
||||
assert!(registry.has_handler(TOOL_SEARCH_TOOL_NAME, None));
|
||||
assert!(registry.has_handler(alias.as_str(), None));
|
||||
assert!(registry.has_handler(TOOL_SEARCH_TOOL_NAME, /*namespace*/ None));
|
||||
assert!(registry.has_handler(alias.as_str(), /*namespace*/ None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2174,8 +2356,8 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
None,
|
||||
None,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
Some(discoverable_tools),
|
||||
&[],
|
||||
)
|
||||
@@ -2269,7 +2451,7 @@ fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2327,7 +2509,7 @@ fn test_mcp_tool_integer_normalized_to_number() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2384,7 +2566,7 @@ fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2445,7 +2627,7 @@ fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2473,7 +2655,7 @@ fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
|
||||
#[test]
|
||||
fn test_shell_tool() {
|
||||
let tool = super::create_shell_tool(false);
|
||||
let tool = super::create_shell_tool(/*exec_permission_approvals_enabled*/ false);
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description, name, ..
|
||||
}) = &tool
|
||||
@@ -2506,7 +2688,9 @@ Examples of valid command strings:
|
||||
|
||||
#[test]
|
||||
fn test_exec_command_tool_windows_description_includes_shell_safety_guidance() {
|
||||
let tool = super::create_exec_command_tool(true, false);
|
||||
let tool = super::create_exec_command_tool(
|
||||
/*allow_login_shell*/ true, /*exec_permission_approvals_enabled*/ false,
|
||||
);
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description, name, ..
|
||||
}) = &tool
|
||||
@@ -2529,7 +2713,7 @@ fn test_exec_command_tool_windows_description_includes_shell_safety_guidance() {
|
||||
|
||||
#[test]
|
||||
fn shell_tool_with_request_permission_includes_additional_permissions() {
|
||||
let tool = super::create_shell_tool(true);
|
||||
let tool = super::create_shell_tool(/*exec_permission_approvals_enabled*/ true);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = tool else {
|
||||
panic!("expected function tool");
|
||||
};
|
||||
@@ -2609,7 +2793,9 @@ fn request_permissions_tool_includes_full_permission_schema() {
|
||||
|
||||
#[test]
|
||||
fn test_shell_command_tool() {
|
||||
let tool = super::create_shell_command_tool(true, false);
|
||||
let tool = super::create_shell_command_tool(
|
||||
/*allow_login_shell*/ true, /*exec_permission_approvals_enabled*/ false,
|
||||
);
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description, name, ..
|
||||
}) = &tool
|
||||
@@ -2686,7 +2872,7 @@ fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2766,7 +2952,13 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) =
|
||||
&find_tool(&tools, "view_image").spec
|
||||
else {
|
||||
@@ -2814,7 +3006,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -2863,7 +3055,13 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
else {
|
||||
panic!("expected freeform tool");
|
||||
@@ -2895,7 +3093,13 @@ fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only(
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
else {
|
||||
panic!("expected freeform tool");
|
||||
|
||||
@@ -6,11 +6,13 @@ use pretty_assertions::assert_eq;
|
||||
fn split_valid_utf8_prefix_respects_max_bytes_for_ascii() {
|
||||
let mut buf = b"hello word!".to_vec();
|
||||
|
||||
let first = split_valid_utf8_prefix_with_max(&mut buf, 5).expect("expected prefix");
|
||||
let first =
|
||||
split_valid_utf8_prefix_with_max(&mut buf, /*max_bytes*/ 5).expect("expected prefix");
|
||||
assert_eq!(first, b"hello".to_vec());
|
||||
assert_eq!(buf, b" word!".to_vec());
|
||||
|
||||
let second = split_valid_utf8_prefix_with_max(&mut buf, 5).expect("expected prefix");
|
||||
let second =
|
||||
split_valid_utf8_prefix_with_max(&mut buf, /*max_bytes*/ 5).expect("expected prefix");
|
||||
assert_eq!(second, b" word".to_vec());
|
||||
assert_eq!(buf, b"!".to_vec());
|
||||
}
|
||||
@@ -20,7 +22,8 @@ fn split_valid_utf8_prefix_avoids_splitting_utf8_codepoints() {
|
||||
// "é" is 2 bytes in UTF-8. With a max of 3 bytes, we should only emit 1 char (2 bytes).
|
||||
let mut buf = "ééé".as_bytes().to_vec();
|
||||
|
||||
let first = split_valid_utf8_prefix_with_max(&mut buf, 3).expect("expected prefix");
|
||||
let first =
|
||||
split_valid_utf8_prefix_with_max(&mut buf, /*max_bytes*/ 3).expect("expected prefix");
|
||||
assert_eq!(std::str::from_utf8(&first).unwrap(), "é");
|
||||
assert_eq!(buf, "éé".as_bytes().to_vec());
|
||||
}
|
||||
@@ -29,7 +32,8 @@ fn split_valid_utf8_prefix_avoids_splitting_utf8_codepoints() {
|
||||
fn split_valid_utf8_prefix_makes_progress_on_invalid_utf8() {
|
||||
let mut buf = vec![0xff, b'a', b'b'];
|
||||
|
||||
let first = split_valid_utf8_prefix_with_max(&mut buf, 2).expect("expected prefix");
|
||||
let first =
|
||||
split_valid_utf8_prefix_with_max(&mut buf, /*max_bytes*/ 2).expect("expected prefix");
|
||||
assert_eq!(first, vec![0xff]);
|
||||
assert_eq!(buf, b"ab".to_vec());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn keeps_prefix_and_suffix_when_over_budget() {
|
||||
let mut buf = HeadTailBuffer::new(10);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
assert_eq!(buf.omitted_bytes(), 0);
|
||||
@@ -20,7 +20,7 @@ fn keeps_prefix_and_suffix_when_over_budget() {
|
||||
|
||||
#[test]
|
||||
fn max_bytes_zero_drops_everything() {
|
||||
let mut buf = HeadTailBuffer::new(0);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 0);
|
||||
buf.push_chunk(b"abc".to_vec());
|
||||
|
||||
assert_eq!(buf.retained_bytes(), 0);
|
||||
@@ -31,7 +31,7 @@ fn max_bytes_zero_drops_everything() {
|
||||
|
||||
#[test]
|
||||
fn head_budget_zero_keeps_only_last_byte_in_tail() {
|
||||
let mut buf = HeadTailBuffer::new(1);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 1);
|
||||
buf.push_chunk(b"abc".to_vec());
|
||||
|
||||
assert_eq!(buf.retained_bytes(), 1);
|
||||
@@ -41,7 +41,7 @@ fn head_budget_zero_keeps_only_last_byte_in_tail() {
|
||||
|
||||
#[test]
|
||||
fn draining_resets_state() {
|
||||
let mut buf = HeadTailBuffer::new(10);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
buf.push_chunk(b"ab".to_vec());
|
||||
|
||||
@@ -55,7 +55,7 @@ fn draining_resets_state() {
|
||||
|
||||
#[test]
|
||||
fn chunk_larger_than_tail_budget_keeps_only_tail_end() {
|
||||
let mut buf = HeadTailBuffer::new(10);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
buf.push_chunk(b"0123456789".to_vec());
|
||||
|
||||
// Tail budget is 5 bytes. This chunk should replace the tail and keep only its last 5 bytes.
|
||||
@@ -69,7 +69,7 @@ fn chunk_larger_than_tail_budget_keeps_only_tail_end() {
|
||||
|
||||
#[test]
|
||||
fn fills_head_then_tail_across_multiple_chunks() {
|
||||
let mut buf = HeadTailBuffer::new(10);
|
||||
let mut buf = HeadTailBuffer::new(/*max_bytes*/ 10);
|
||||
|
||||
// Fill the 5-byte head budget across multiple chunks.
|
||||
buf.push_chunk(b"01".to_vec());
|
||||
|
||||
@@ -33,7 +33,15 @@ async fn exec_command(
|
||||
yield_time_ms: u64,
|
||||
workdir: Option<PathBuf>,
|
||||
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
|
||||
exec_command_with_tty(session, turn, cmd, yield_time_ms, workdir, true).await
|
||||
exec_command_with_tty(
|
||||
session,
|
||||
turn,
|
||||
cmd,
|
||||
yield_time_ms,
|
||||
workdir,
|
||||
/*tty*/ true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn shell_env() -> HashMap<String, String> {
|
||||
@@ -227,14 +235,17 @@ async fn unified_exec_persists_across_requests() -> anyhow::Result<()> {
|
||||
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
|
||||
let open_shell = exec_command(&session, &turn, "bash -i", 2_500, None).await?;
|
||||
let open_shell = exec_command(
|
||||
&session, &turn, "bash -i", /*yield_time_ms*/ 2_500, /*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
let process_id = open_shell.process_id.expect("expected process_id");
|
||||
|
||||
write_stdin(
|
||||
&session,
|
||||
process_id,
|
||||
"export CODEX_INTERACTIVE_SHELL_VAR=codex\n",
|
||||
2_500,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -242,7 +253,7 @@ async fn unified_exec_persists_across_requests() -> anyhow::Result<()> {
|
||||
&session,
|
||||
process_id,
|
||||
"echo $CODEX_INTERACTIVE_SHELL_VAR\n",
|
||||
2_500,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
@@ -259,14 +270,17 @@ async fn multi_unified_exec_sessions() -> anyhow::Result<()> {
|
||||
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
|
||||
let shell_a = exec_command(&session, &turn, "bash -i", 2_500, None).await?;
|
||||
let shell_a = exec_command(
|
||||
&session, &turn, "bash -i", /*yield_time_ms*/ 2_500, /*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
let session_a = shell_a.process_id.expect("expected process id");
|
||||
|
||||
write_stdin(
|
||||
&session,
|
||||
session_a,
|
||||
"export CODEX_INTERACTIVE_SHELL_VAR=codex\n",
|
||||
2_500,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -274,8 +288,8 @@ async fn multi_unified_exec_sessions() -> anyhow::Result<()> {
|
||||
&session,
|
||||
&turn,
|
||||
"echo $CODEX_INTERACTIVE_SHELL_VAR",
|
||||
2_500,
|
||||
None,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
/*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
@@ -292,7 +306,7 @@ async fn multi_unified_exec_sessions() -> anyhow::Result<()> {
|
||||
&session,
|
||||
shell_a.process_id.expect("expected process id"),
|
||||
"echo $CODEX_INTERACTIVE_SHELL_VAR\n",
|
||||
2_500,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
@@ -311,14 +325,17 @@ async fn unified_exec_timeouts() -> anyhow::Result<()> {
|
||||
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
|
||||
let open_shell = exec_command(&session, &turn, "bash -i", 2_500, None).await?;
|
||||
let open_shell = exec_command(
|
||||
&session, &turn, "bash -i", /*yield_time_ms*/ 2_500, /*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
let process_id = open_shell.process_id.expect("expected process id");
|
||||
|
||||
write_stdin(
|
||||
&session,
|
||||
process_id,
|
||||
format!("export CODEX_INTERACTIVE_SHELL_VAR={TEST_VAR_VALUE}\n").as_str(),
|
||||
2_500,
|
||||
/*yield_time_ms*/ 2_500,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -326,7 +343,7 @@ async fn unified_exec_timeouts() -> anyhow::Result<()> {
|
||||
&session,
|
||||
process_id,
|
||||
"sleep 5 && echo $CODEX_INTERACTIVE_SHELL_VAR\n",
|
||||
10,
|
||||
/*yield_time_ms*/ 10,
|
||||
)
|
||||
.await?;
|
||||
assert!(
|
||||
@@ -336,7 +353,7 @@ async fn unified_exec_timeouts() -> anyhow::Result<()> {
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(7)).await;
|
||||
|
||||
let out_3 = write_stdin(&session, process_id, "", 100).await?;
|
||||
let out_3 = write_stdin(&session, process_id, "", /*yield_time_ms*/ 100).await?;
|
||||
|
||||
assert!(
|
||||
out_3.truncated_output().contains(TEST_VAR_VALUE),
|
||||
@@ -351,12 +368,12 @@ async fn unified_exec_pause_blocks_yield_timeout() -> anyhow::Result<()> {
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
session.set_out_of_band_elicitation_pause_state(true);
|
||||
session.set_out_of_band_elicitation_pause_state(/*paused*/ true);
|
||||
|
||||
let paused_session = Arc::clone(&session);
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
paused_session.set_out_of_band_elicitation_pause_state(false);
|
||||
paused_session.set_out_of_band_elicitation_pause_state(/*paused*/ false);
|
||||
});
|
||||
|
||||
let started = tokio::time::Instant::now();
|
||||
@@ -364,8 +381,8 @@ async fn unified_exec_pause_blocks_yield_timeout() -> anyhow::Result<()> {
|
||||
&session,
|
||||
&turn,
|
||||
"sleep 1 && echo unified-exec-done",
|
||||
250,
|
||||
None,
|
||||
/*yield_time_ms*/ 250,
|
||||
/*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -390,7 +407,14 @@ async fn unified_exec_pause_blocks_yield_timeout() -> anyhow::Result<()> {
|
||||
async fn requests_with_large_timeout_are_capped() -> anyhow::Result<()> {
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
|
||||
let result = exec_command(&session, &turn, "echo codex", 120_000, None).await?;
|
||||
let result = exec_command(
|
||||
&session,
|
||||
&turn,
|
||||
"echo codex",
|
||||
/*yield_time_ms*/ 120_000,
|
||||
/*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(result.process_id.is_some());
|
||||
assert!(result.truncated_output().contains("codex"));
|
||||
@@ -402,7 +426,14 @@ async fn requests_with_large_timeout_are_capped() -> anyhow::Result<()> {
|
||||
#[ignore] // Ignored while we have a better way to test this.
|
||||
async fn completed_commands_do_not_persist_sessions() -> anyhow::Result<()> {
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
let result = exec_command(&session, &turn, "echo codex", 2_500, None).await?;
|
||||
let result = exec_command(
|
||||
&session,
|
||||
&turn,
|
||||
"echo codex",
|
||||
/*yield_time_ms*/ 2_500,
|
||||
/*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
result.process_id.is_some(),
|
||||
@@ -430,14 +461,17 @@ async fn reusing_completed_process_returns_unknown_process() -> anyhow::Result<(
|
||||
|
||||
let (session, turn) = test_session_and_turn().await;
|
||||
|
||||
let open_shell = exec_command(&session, &turn, "bash -i", 2_500, None).await?;
|
||||
let open_shell = exec_command(
|
||||
&session, &turn, "bash -i", /*yield_time_ms*/ 2_500, /*workdir*/ None,
|
||||
)
|
||||
.await?;
|
||||
let process_id = open_shell.process_id.expect("expected process id");
|
||||
|
||||
write_stdin(&session, process_id, "exit\n", 2_500).await?;
|
||||
write_stdin(&session, process_id, "exit\n", /*yield_time_ms*/ 2_500).await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
let err = write_stdin(&session, process_id, "", 100)
|
||||
let err = write_stdin(&session, process_id, "", /*yield_time_ms*/ 100)
|
||||
.await
|
||||
.expect_err("expected unknown process error");
|
||||
|
||||
@@ -475,9 +509,9 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> {
|
||||
let environment = codex_exec_server::Environment::default();
|
||||
let process = UnifiedExecProcessManager::default()
|
||||
.open_session_with_exec_env(
|
||||
1234,
|
||||
/*process_id*/ 1234,
|
||||
&request,
|
||||
false,
|
||||
/*tty*/ false,
|
||||
Box::new(NoopSpawnLifecycle),
|
||||
&environment,
|
||||
)
|
||||
@@ -517,9 +551,9 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul
|
||||
let manager = UnifiedExecProcessManager::default();
|
||||
let process = manager
|
||||
.open_session_with_exec_env(
|
||||
1234,
|
||||
/*process_id*/ 1234,
|
||||
&request,
|
||||
true,
|
||||
/*tty*/ true,
|
||||
Box::new(NoopSpawnLifecycle),
|
||||
remote_test_env.environment(),
|
||||
)
|
||||
@@ -541,7 +575,7 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul
|
||||
&output_closed,
|
||||
&output_closed_notify,
|
||||
&cancellation_token,
|
||||
None,
|
||||
/*pause_state*/ None,
|
||||
Instant::now() + Duration::from_millis(2_500),
|
||||
)
|
||||
.await;
|
||||
@@ -571,9 +605,9 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()
|
||||
let manager = UnifiedExecProcessManager::default();
|
||||
let err = manager
|
||||
.open_session_with_exec_env(
|
||||
1234,
|
||||
/*process_id*/ 1234,
|
||||
&request,
|
||||
true,
|
||||
/*tty*/ true,
|
||||
Box::new(TestSpawnLifecycle {
|
||||
inherited_fds: vec![42],
|
||||
}),
|
||||
|
||||
@@ -36,7 +36,7 @@ fn unified_exec_env_overrides_existing_values() {
|
||||
|
||||
#[test]
|
||||
fn exec_server_process_id_matches_unified_exec_process_id() {
|
||||
assert_eq!(exec_server_process_id(4321), "4321");
|
||||
assert_eq!(exec_server_process_id(/*process_id*/ 4321), "4321");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -232,9 +232,9 @@ fn emit_feedback_auth_recovery_tags_clears_stale_401_fields() {
|
||||
"done",
|
||||
"recovery_not_run",
|
||||
Some("req-401-b"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
/*auth_cf_ray*/ None,
|
||||
/*auth_error*/ None,
|
||||
/*auth_error_code*/ None,
|
||||
);
|
||||
|
||||
let tags = tags.lock().unwrap().clone();
|
||||
@@ -440,7 +440,7 @@ fn resume_command_prefers_name_over_id() {
|
||||
#[test]
|
||||
fn resume_command_with_only_id() {
|
||||
let thread_id = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
|
||||
let command = resume_command(None, Some(thread_id));
|
||||
let command = resume_command(/*thread_name*/ None, Some(thread_id));
|
||||
assert_eq!(
|
||||
command,
|
||||
Some("codex resume 123e4567-e89b-12d3-a456-426614174000".to_string())
|
||||
@@ -449,21 +449,21 @@ fn resume_command_with_only_id() {
|
||||
|
||||
#[test]
|
||||
fn resume_command_with_no_name_or_id() {
|
||||
let command = resume_command(None, None);
|
||||
let command = resume_command(/*thread_name*/ None, /*thread_id*/ None);
|
||||
assert_eq!(command, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_command_quotes_thread_name_when_needed() {
|
||||
let command = resume_command(Some("-starts-with-dash"), None);
|
||||
let command = resume_command(Some("-starts-with-dash"), /*thread_id*/ None);
|
||||
assert_eq!(
|
||||
command,
|
||||
Some("codex resume -- -starts-with-dash".to_string())
|
||||
);
|
||||
|
||||
let command = resume_command(Some("two words"), None);
|
||||
let command = resume_command(Some("two words"), /*thread_id*/ None);
|
||||
assert_eq!(command, Some("codex resume 'two words'".to_string()));
|
||||
|
||||
let command = resume_command(Some("quote'case"), None);
|
||||
let command = resume_command(Some("quote'case"), /*thread_id*/ None);
|
||||
assert_eq!(command, Some("codex resume \"quote'case\"".to_string()));
|
||||
}
|
||||
|
||||
@@ -52,8 +52,11 @@ fn elevated_wins_when_both_flags_are_enabled() {
|
||||
#[test]
|
||||
fn legacy_mode_prefers_elevated() {
|
||||
let mut entries = BTreeMap::new();
|
||||
entries.insert("experimental_windows_sandbox".to_string(), true);
|
||||
entries.insert("elevated_windows_sandbox".to_string(), true);
|
||||
entries.insert(
|
||||
"experimental_windows_sandbox".to_string(),
|
||||
/*value*/ true,
|
||||
);
|
||||
entries.insert("elevated_windows_sandbox".to_string(), /*value*/ true);
|
||||
|
||||
assert_eq!(
|
||||
legacy_windows_sandbox_mode_from_entries(&entries),
|
||||
@@ -64,7 +67,10 @@ fn legacy_mode_prefers_elevated() {
|
||||
#[test]
|
||||
fn legacy_mode_supports_alias_key() {
|
||||
let mut entries = BTreeMap::new();
|
||||
entries.insert("enable_experimental_windows_sandbox".to_string(), true);
|
||||
entries.insert(
|
||||
"enable_experimental_windows_sandbox".to_string(),
|
||||
/*value*/ true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
legacy_windows_sandbox_mode_from_entries(&entries),
|
||||
@@ -98,7 +104,10 @@ fn resolve_windows_sandbox_mode_prefers_profile_windows() {
|
||||
#[test]
|
||||
fn resolve_windows_sandbox_mode_falls_back_to_legacy_keys() {
|
||||
let mut entries = BTreeMap::new();
|
||||
entries.insert("experimental_windows_sandbox".to_string(), true);
|
||||
entries.insert(
|
||||
"experimental_windows_sandbox".to_string(),
|
||||
/*value*/ true,
|
||||
);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml { entries }),
|
||||
..Default::default()
|
||||
@@ -113,7 +122,10 @@ fn resolve_windows_sandbox_mode_falls_back_to_legacy_keys() {
|
||||
#[test]
|
||||
fn resolve_windows_sandbox_mode_profile_legacy_false_blocks_top_level_legacy_true() {
|
||||
let mut profile_entries = BTreeMap::new();
|
||||
profile_entries.insert("experimental_windows_sandbox".to_string(), false);
|
||||
profile_entries.insert(
|
||||
"experimental_windows_sandbox".to_string(),
|
||||
/*value*/ false,
|
||||
);
|
||||
let profile = ConfigProfile {
|
||||
features: Some(FeaturesToml {
|
||||
entries: profile_entries,
|
||||
@@ -122,7 +134,10 @@ fn resolve_windows_sandbox_mode_profile_legacy_false_blocks_top_level_legacy_tru
|
||||
};
|
||||
|
||||
let mut cfg_entries = BTreeMap::new();
|
||||
cfg_entries.insert("experimental_windows_sandbox".to_string(), true);
|
||||
cfg_entries.insert(
|
||||
"experimental_windows_sandbox".to_string(),
|
||||
/*value*/ true,
|
||||
);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml {
|
||||
entries: cfg_entries,
|
||||
|
||||
@@ -77,24 +77,24 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
conversation_id,
|
||||
model.as_str(),
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(auth_mode),
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
session_source.clone(),
|
||||
);
|
||||
|
||||
let client = ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
);
|
||||
let mut client_session = client.new_session();
|
||||
|
||||
@@ -116,8 +116,8 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
&session_telemetry,
|
||||
effort,
|
||||
summary.unwrap_or(model_info.default_reasoning_summary),
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("stream failed");
|
||||
@@ -190,24 +190,24 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
conversation_id,
|
||||
model.as_str(),
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(auth_mode),
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
session_source.clone(),
|
||||
);
|
||||
|
||||
let client = ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
);
|
||||
let mut client_session = client.new_session();
|
||||
|
||||
@@ -229,8 +229,8 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
&session_telemetry,
|
||||
effort,
|
||||
summary.unwrap_or(model_info.default_reasoning_summary),
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("stream failed");
|
||||
@@ -302,24 +302,24 @@ async fn responses_respects_model_info_overrides_from_config() {
|
||||
conversation_id,
|
||||
model.as_str(),
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
auth_mode,
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
session_source.clone(),
|
||||
);
|
||||
|
||||
let client = ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
provider.clone(),
|
||||
session_source,
|
||||
config.model_verbosity,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
);
|
||||
let mut client_session = client.new_session();
|
||||
|
||||
@@ -341,8 +341,8 @@ async fn responses_respects_model_info_overrides_from_config() {
|
||||
&session_telemetry,
|
||||
effort,
|
||||
summary.unwrap_or(model_info.default_reasoning_summary),
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("stream failed");
|
||||
|
||||
@@ -268,7 +268,7 @@ async fn report_agent_job_result_rejects_wrong_thread() -> Result<()> {
|
||||
.expect("job_id from csv");
|
||||
let job = db.get_agent_job(job_id.as_str()).await?.expect("job");
|
||||
let items = db
|
||||
.list_agent_job_items(job.id.as_str(), None, Some(10))
|
||||
.list_agent_job_items(job.id.as_str(), /*status*/ None, Some(10))
|
||||
.await?;
|
||||
let item = items.first().expect("item");
|
||||
let wrong_thread_id = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
@@ -273,7 +273,10 @@ async fn websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm()
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
|
||||
let warmup = server.wait_for_request(0, 0).await.body_json();
|
||||
let warmup = server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 0)
|
||||
.await
|
||||
.body_json();
|
||||
assert_eq!(warmup["type"].as_str(), Some("response.create"));
|
||||
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
||||
assert_eq!(warmup.get("service_tier"), None);
|
||||
@@ -326,12 +329,16 @@ async fn websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm() -> Resu
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
|
||||
let warmup = server.wait_for_request(0, 0).await.body_json();
|
||||
let warmup = server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 0)
|
||||
.await
|
||||
.body_json();
|
||||
assert_eq!(warmup["type"].as_str(), Some("response.create"));
|
||||
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
||||
assert_eq!(warmup["service_tier"].as_str(), Some("priority"));
|
||||
|
||||
test.submit_turn_with_service_tier("hello", None).await?;
|
||||
test.submit_turn_with_service_tier("hello", /*service_tier*/ None)
|
||||
.await?;
|
||||
|
||||
assert_eq!(server.handshakes().len(), 1);
|
||||
let connection = server.single_connection();
|
||||
@@ -382,14 +389,18 @@ async fn websocket_v2_next_turn_uses_updated_service_tier() -> Result<()> {
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
|
||||
let warmup = server.wait_for_request(0, 0).await.body_json();
|
||||
let warmup = server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 0)
|
||||
.await
|
||||
.body_json();
|
||||
assert_eq!(warmup["type"].as_str(), Some("response.create"));
|
||||
assert_eq!(warmup["generate"].as_bool(), Some(false));
|
||||
assert_eq!(warmup.get("service_tier"), None);
|
||||
|
||||
test.submit_turn_with_service_tier("first", Some(ServiceTier::Fast))
|
||||
.await?;
|
||||
test.submit_turn_with_service_tier("second", None).await?;
|
||||
test.submit_turn_with_service_tier("second", /*service_tier*/ None)
|
||||
.await?;
|
||||
|
||||
assert_eq!(server.handshakes().len(), 1);
|
||||
let connection = server.single_connection();
|
||||
|
||||
@@ -131,7 +131,12 @@ impl ActionKind {
|
||||
"from pathlib import Path; path = Path({path_str:?}); content = {content:?}; path.write_text(content, encoding='utf-8'); print(path.read_text(encoding='utf-8'), end='')",
|
||||
);
|
||||
let command = format!("python3 -c {script:?}");
|
||||
let event = shell_event(call_id, &command, 5_000, sandbox_permissions)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
&command,
|
||||
/*timeout_ms*/ 5_000,
|
||||
sandbox_permissions,
|
||||
)?;
|
||||
Ok((event, Some(command)))
|
||||
}
|
||||
ActionKind::FetchUrl {
|
||||
@@ -153,7 +158,12 @@ impl ActionKind {
|
||||
);
|
||||
|
||||
let command = format!("python3 -c \"{script}\"");
|
||||
let event = shell_event(call_id, &command, 5_000, sandbox_permissions)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
&command,
|
||||
/*timeout_ms*/ 5_000,
|
||||
sandbox_permissions,
|
||||
)?;
|
||||
Ok((event, Some(command)))
|
||||
}
|
||||
ActionKind::FetchUrlNoProxy {
|
||||
@@ -175,11 +185,21 @@ impl ActionKind {
|
||||
);
|
||||
|
||||
let command = format!("python3 -c \"{script}\"");
|
||||
let event = shell_event(call_id, &command, 5_000, sandbox_permissions)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
&command,
|
||||
/*timeout_ms*/ 5_000,
|
||||
sandbox_permissions,
|
||||
)?;
|
||||
Ok((event, Some(command)))
|
||||
}
|
||||
ActionKind::RunCommand { command } => {
|
||||
let event = shell_event(call_id, command, 2_000, sandbox_permissions)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
command,
|
||||
/*timeout_ms*/ 2_000,
|
||||
sandbox_permissions,
|
||||
)?;
|
||||
Ok((event, Some(command.to_string())))
|
||||
}
|
||||
ActionKind::RunUnifiedExecCommand {
|
||||
@@ -206,7 +226,12 @@ impl ActionKind {
|
||||
let _ = fs::remove_file(&path);
|
||||
let patch = build_add_file_patch(&patch_path, content);
|
||||
let command = shell_apply_patch_command(&patch);
|
||||
let event = shell_event(call_id, &command, 5_000, sandbox_permissions)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
&command,
|
||||
/*timeout_ms*/ 5_000,
|
||||
sandbox_permissions,
|
||||
)?;
|
||||
Ok((event, Some(command)))
|
||||
}
|
||||
}
|
||||
@@ -233,7 +258,13 @@ fn shell_event(
|
||||
timeout_ms: u64,
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
) -> Result<Value> {
|
||||
shell_event_with_prefix_rule(call_id, command, timeout_ms, sandbox_permissions, None)
|
||||
shell_event_with_prefix_rule(
|
||||
call_id,
|
||||
command,
|
||||
timeout_ms,
|
||||
sandbox_permissions,
|
||||
/*prefix_rule*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
fn shell_event_with_prefix_rule(
|
||||
@@ -2259,7 +2290,12 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> {
|
||||
.await?;
|
||||
|
||||
let call_id = "zsh-fork-prefix-rule-unsandboxed";
|
||||
let event = shell_event(call_id, &command, 1_000, SandboxPermissions::UseDefault)?;
|
||||
let event = shell_event(
|
||||
call_id,
|
||||
&command,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::UseDefault,
|
||||
)?;
|
||||
let _ = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
@@ -2318,7 +2354,7 @@ async fn invalid_requested_prefix_rule_falls_back_for_compound_command() -> Resu
|
||||
let event = shell_event_with_prefix_rule(
|
||||
call_id,
|
||||
command,
|
||||
1_000,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
Some(vec!["touch".to_string()]),
|
||||
)?;
|
||||
@@ -2369,7 +2405,7 @@ async fn approving_fallback_rule_for_compound_command_works() -> Result<()> {
|
||||
let event = shell_event_with_prefix_rule(
|
||||
call_id,
|
||||
command,
|
||||
1_000,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
Some(vec!["touch".to_string()]),
|
||||
)?;
|
||||
@@ -2416,7 +2452,7 @@ async fn approving_fallback_rule_for_compound_command_works() -> Result<()> {
|
||||
let event = shell_event_with_prefix_rule(
|
||||
call_id,
|
||||
command,
|
||||
1_000,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
Some(vec!["touch".to_string()]),
|
||||
)?;
|
||||
@@ -2498,7 +2534,10 @@ allow_local_binding = true
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
let layers = config
|
||||
.config_layer_stack
|
||||
.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true)
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::LowestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
@@ -2542,7 +2581,7 @@ allow_local_binding = true
|
||||
let first_event = shell_event(
|
||||
call_id_first,
|
||||
&fetch_command,
|
||||
30_000,
|
||||
/*timeout_ms*/ 30_000,
|
||||
SandboxPermissions::UseDefault,
|
||||
)?;
|
||||
|
||||
@@ -2682,7 +2721,7 @@ allow_local_binding = true
|
||||
let second_event = shell_event(
|
||||
call_id_second,
|
||||
&fetch_command,
|
||||
30_000,
|
||||
/*timeout_ms*/ 30_000,
|
||||
SandboxPermissions::UseDefault,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -960,7 +960,7 @@ impl RefreshTokenTestContext {
|
||||
|
||||
let auth_manager = AuthManager::shared(
|
||||
codex_home.path().to_path_buf(),
|
||||
false,
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
AuthCredentialsStoreMode::File,
|
||||
);
|
||||
|
||||
|
||||
@@ -718,7 +718,8 @@ async fn chatgpt_auth_sends_correct_request() {
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut model_provider = built_in_model_providers(/* openai_base_url */ None)["openai"].clone();
|
||||
let mut model_provider =
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone();
|
||||
model_provider.base_url = Some(format!("{}/api/codex", server.uri()));
|
||||
model_provider.supports_websockets = false;
|
||||
let mut builder = test_codex()
|
||||
@@ -796,7 +797,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
|
||||
let model_provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
supports_websockets: false,
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
|
||||
// Init session
|
||||
@@ -1825,24 +1826,24 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
conversation_id,
|
||||
model.as_str(),
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
auth_manager.auth_mode().map(TelemetryAuthMode::from),
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
SessionSource::Exec,
|
||||
);
|
||||
|
||||
let client = ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
provider.clone(),
|
||||
SessionSource::Exec,
|
||||
config.model_verbosity,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
/*enable_request_compression*/ false,
|
||||
/*include_timing_metrics*/ false,
|
||||
/*beta_features_header*/ None,
|
||||
);
|
||||
let mut client_session = client.new_session();
|
||||
|
||||
@@ -1917,8 +1918,8 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
&session_telemetry,
|
||||
effort,
|
||||
summary.unwrap_or(ReasoningSummary::Auto),
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("responses stream to start");
|
||||
@@ -1957,7 +1958,10 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
skip_if_no_network!();
|
||||
let server = MockServer::start().await;
|
||||
|
||||
let sse_body = sse(vec![ev_completed_with_tokens("resp_rate", 123)]);
|
||||
let sse_body = sse(vec![ev_completed_with_tokens(
|
||||
"resp_rate",
|
||||
/*total_tokens*/ 123,
|
||||
)]);
|
||||
|
||||
let response = ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
@@ -1976,7 +1980,8 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut provider = built_in_model_providers(/* openai_base_url */ None)["openai"].clone();
|
||||
let mut provider =
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone();
|
||||
provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
provider.supports_websockets = false;
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ async fn responses_websocket_streams_without_feature_flag_when_provider_supports
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, false).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ false).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
|
||||
@@ -292,7 +292,7 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, true).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
client_session
|
||||
@@ -302,8 +302,8 @@ async fn responses_websocket_request_prewarm_reuses_connection() {
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket prewarm failed");
|
||||
@@ -383,8 +383,8 @@ async fn responses_websocket_preconnect_is_reused_even_with_header_changes() {
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -411,7 +411,7 @@ async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes(
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, true).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
client_session
|
||||
@@ -421,8 +421,8 @@ async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes(
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket prewarm failed");
|
||||
@@ -433,8 +433,8 @@ async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes(
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -476,7 +476,7 @@ async fn responses_websocket_prewarm_uses_v2_when_provider_supports_websockets()
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, false).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ false).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
client_session
|
||||
@@ -486,8 +486,8 @@ async fn responses_websocket_prewarm_uses_v2_when_provider_supports_websockets()
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket prewarm failed");
|
||||
@@ -533,7 +533,7 @@ async fn responses_websocket_preconnect_runs_when_only_v2_feature_enabled() {
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, true).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
client_session
|
||||
.preconnect_websocket(&harness.session_telemetry, &harness.model_info)
|
||||
@@ -576,7 +576,7 @@ async fn responses_websocket_v2_requests_use_v2_when_provider_supports_websocket
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, true).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![
|
||||
@@ -625,7 +625,7 @@ async fn responses_websocket_v2_incremental_requests_are_reused_across_turns() {
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, false).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ false).await;
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![
|
||||
message_item("hello"),
|
||||
@@ -669,7 +669,7 @@ async fn responses_websocket_v2_wins_when_both_features_enabled() {
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_options(&server, false).await;
|
||||
let harness = websocket_harness_with_options(&server, /*runtime_metrics_enabled*/ false).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![
|
||||
@@ -757,7 +757,8 @@ async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_runtime_metrics(&server, true).await;
|
||||
let harness =
|
||||
websocket_harness_with_runtime_metrics(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
harness.session_telemetry.reset_runtime_metrics();
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
@@ -795,7 +796,8 @@ async fn responses_websocket_omits_timing_metrics_header_when_runtime_metrics_di
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_runtime_metrics(&server, false).await;
|
||||
let harness =
|
||||
websocket_harness_with_runtime_metrics(&server, /*runtime_metrics_enabled*/ false).await;
|
||||
let mut client_session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
|
||||
@@ -833,8 +835,8 @@ async fn responses_websocket_emits_reasoning_included_event() {
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -906,8 +908,8 @@ async fn responses_websocket_emits_rate_limit_events() {
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -1213,7 +1215,7 @@ async fn responses_websocket_forwards_turn_metadata_on_initial_and_incremental_c
|
||||
&mut client_session,
|
||||
&harness,
|
||||
&prompt_one,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
Some(first_turn_metadata),
|
||||
)
|
||||
.await;
|
||||
@@ -1221,7 +1223,7 @@ async fn responses_websocket_forwards_turn_metadata_on_initial_and_incremental_c
|
||||
&mut client_session,
|
||||
&harness,
|
||||
&prompt_two,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
Some(enriched_turn_metadata),
|
||||
)
|
||||
.await;
|
||||
@@ -1381,7 +1383,7 @@ async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix() {
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_v2(&server, true).await;
|
||||
let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut session = harness.client.new_session();
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![
|
||||
@@ -1420,7 +1422,7 @@ async fn responses_websocket_v2_creates_without_previous_response_id_when_non_in
|
||||
]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_v2(&server, true).await;
|
||||
let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut session = harness.client.new_session();
|
||||
let prompt_one =
|
||||
prompt_with_input_and_instructions(vec![message_item("hello")], "base instructions one");
|
||||
@@ -1467,7 +1469,7 @@ async fn responses_websocket_v2_after_error_uses_full_create_without_previous_re
|
||||
])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_v2(&server, true).await;
|
||||
let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut session = harness.client.new_session();
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]);
|
||||
@@ -1486,8 +1488,8 @@ async fn responses_websocket_v2_after_error_uses_full_create_without_previous_re
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -1559,7 +1561,7 @@ async fn responses_websocket_v2_surfaces_terminal_error_without_close_handshake(
|
||||
}])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_v2(&server, true).await;
|
||||
let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut session = harness.client.new_session();
|
||||
let prompt_one = prompt_with_input(vec![message_item("hello")]);
|
||||
let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]);
|
||||
@@ -1573,8 +1575,8 @@ async fn responses_websocket_v2_surfaces_terminal_error_without_close_handshake(
|
||||
&harness.session_telemetry,
|
||||
harness.effort,
|
||||
harness.summary,
|
||||
None,
|
||||
None,
|
||||
/*service_tier*/ None,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("websocket stream failed");
|
||||
@@ -1605,7 +1607,7 @@ async fn responses_websocket_v2_sets_openai_beta_header() {
|
||||
]]])
|
||||
.await;
|
||||
|
||||
let harness = websocket_harness_with_v2(&server, true).await;
|
||||
let harness = websocket_harness_with_v2(&server, /*runtime_metrics_enabled*/ true).await;
|
||||
let mut session = harness.client.new_session();
|
||||
let prompt = prompt_with_input(vec![message_item("hello")]);
|
||||
|
||||
@@ -1659,7 +1661,7 @@ fn prompt_with_input_and_instructions(input: Vec<ResponseItem>, instructions: &s
|
||||
}
|
||||
|
||||
fn websocket_provider(server: &WebSocketTestServer) -> ModelProviderInfo {
|
||||
websocket_provider_with_connect_timeout(server, None)
|
||||
websocket_provider_with_connect_timeout(server, /*websocket_connect_timeout_ms*/ None)
|
||||
}
|
||||
|
||||
fn websocket_provider_with_connect_timeout(
|
||||
@@ -1686,7 +1688,7 @@ fn websocket_provider_with_connect_timeout(
|
||||
}
|
||||
|
||||
async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness {
|
||||
websocket_harness_with_runtime_metrics(server, false).await
|
||||
websocket_harness_with_runtime_metrics(server, /*runtime_metrics_enabled*/ false).await
|
||||
}
|
||||
|
||||
async fn websocket_harness_with_runtime_metrics(
|
||||
@@ -1739,11 +1741,11 @@ async fn websocket_harness_with_provider_options(
|
||||
conversation_id,
|
||||
MODEL,
|
||||
model_info.slug.as_str(),
|
||||
None,
|
||||
/*account_id*/ None,
|
||||
Some("test@test.com".to_string()),
|
||||
auth_manager.auth_mode().map(TelemetryAuthMode::from),
|
||||
"test_originator".to_string(),
|
||||
false,
|
||||
/*log_user_prompts*/ false,
|
||||
"test".to_string(),
|
||||
SessionSource::Exec,
|
||||
)
|
||||
@@ -1751,14 +1753,14 @@ async fn websocket_harness_with_provider_options(
|
||||
let effort = None;
|
||||
let summary = ReasoningSummary::Auto;
|
||||
let client = ModelClient::new(
|
||||
None,
|
||||
/*auth_manager*/ None,
|
||||
conversation_id,
|
||||
provider.clone(),
|
||||
SessionSource::Exec,
|
||||
config.model_verbosity,
|
||||
false,
|
||||
/*enable_request_compression*/ false,
|
||||
runtime_metrics_enabled,
|
||||
None,
|
||||
/*beta_features_header*/ None,
|
||||
);
|
||||
|
||||
WebsocketTestHarness {
|
||||
@@ -1777,7 +1779,13 @@ async fn stream_until_complete(
|
||||
harness: &WebsocketTestHarness,
|
||||
prompt: &Prompt,
|
||||
) {
|
||||
stream_until_complete_with_service_tier(client_session, harness, prompt, None).await;
|
||||
stream_until_complete_with_service_tier(
|
||||
client_session,
|
||||
harness,
|
||||
prompt,
|
||||
/*service_tier*/ None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn stream_until_complete_with_service_tier(
|
||||
@@ -1786,8 +1794,14 @@ async fn stream_until_complete_with_service_tier(
|
||||
prompt: &Prompt,
|
||||
service_tier: Option<ServiceTier>,
|
||||
) {
|
||||
stream_until_complete_with_turn_metadata(client_session, harness, prompt, service_tier, None)
|
||||
.await;
|
||||
stream_until_complete_with_turn_metadata(
|
||||
client_session,
|
||||
harness,
|
||||
prompt,
|
||||
service_tier,
|
||||
/*turn_metadata_header*/ None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn stream_until_complete_with_turn_metadata(
|
||||
|
||||
@@ -252,7 +252,7 @@ async fn code_mode_can_return_exec_command_output() -> Result<()> {
|
||||
r#"
|
||||
text(JSON.stringify(await tools.exec_command({ cmd: "printf code_mode_exec_marker" })));
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -264,9 +264,9 @@ text(JSON.stringify(await tools.exec_command({ cmd: "printf code_mode_exec_marke
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
let parsed: Value = serde_json::from_str(text_item(&items, 1))?;
|
||||
let parsed: Value = serde_json::from_str(text_item(&items, /*index*/ 1))?;
|
||||
assert!(
|
||||
parsed
|
||||
.get("chunk_id")
|
||||
@@ -378,7 +378,7 @@ const result = await tools.update_plan({
|
||||
});
|
||||
text(JSON.stringify(result));
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -483,7 +483,7 @@ text(JSON.stringify(results));
|
||||
.expect("parallel code mode run should send a completion request");
|
||||
let items = custom_tool_output_items(&req, "call-1");
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(text_item(&items, 1), "[\"ok\",\"ok\"]");
|
||||
assert_eq!(text_item(&items, /*index*/ 1), "[\"ok\",\"ok\"]");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -503,7 +503,7 @@ text(JSON.stringify(await tools.exec_command({
|
||||
max_output_tokens: 100
|
||||
})));
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -515,7 +515,7 @@ text(JSON.stringify(await tools.exec_command({
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
let expected_pattern = r#"(?sx)
|
||||
\A
|
||||
@@ -524,7 +524,7 @@ Total\ output\ lines:\ 1\n
|
||||
.*…\d+\ tokens\ truncated….*
|
||||
\z
|
||||
"#;
|
||||
assert_regex_match(expected_pattern, text_item(&items, 1));
|
||||
assert_regex_match(expected_pattern, text_item(&items, /*index*/ 1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -542,7 +542,7 @@ text("before crash");
|
||||
text("still before crash");
|
||||
throw new Error("boom");
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -554,10 +554,10 @@ throw new Error("boom");
|
||||
r"(?s)\A",
|
||||
r"Script failed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&items, 1), "before crash");
|
||||
assert_eq!(text_item(&items, 2), "still before crash");
|
||||
assert_eq!(text_item(&items, /*index*/ 1), "before crash");
|
||||
assert_eq!(text_item(&items, /*index*/ 2), "still before crash");
|
||||
assert_regex_match(
|
||||
r#"(?sx)
|
||||
\A
|
||||
@@ -566,7 +566,7 @@ Error:\ boom\n
|
||||
(?:\s+at\ .+\n?)+
|
||||
\z
|
||||
"#,
|
||||
text_item(&items, 3),
|
||||
text_item(&items, /*index*/ 3),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -589,7 +589,7 @@ try {
|
||||
text(`caught:${error?.message ?? String(error)}`);
|
||||
}
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -666,10 +666,10 @@ text("phase 3");
|
||||
r"(?s)\A",
|
||||
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&first_items, 0),
|
||||
text_item(&first_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&first_items, 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -707,13 +707,13 @@ text("phase 3");
|
||||
r"(?s)\A",
|
||||
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&second_items, 0),
|
||||
text_item(&second_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(
|
||||
extract_running_cell_id(text_item(&second_items, 0)),
|
||||
extract_running_cell_id(text_item(&second_items, /*index*/ 0)),
|
||||
cell_id
|
||||
);
|
||||
assert_eq!(text_item(&second_items, 1), "phase 2");
|
||||
assert_eq!(text_item(&second_items, /*index*/ 1), "phase 2");
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -751,9 +751,9 @@ text("phase 3");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&third_items, 0),
|
||||
text_item(&third_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&third_items, 1), "phase 3");
|
||||
assert_eq!(text_item(&third_items, /*index*/ 1), "phase 3");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -806,10 +806,10 @@ while (true) {}
|
||||
r"(?s)\A",
|
||||
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&first_items, 0),
|
||||
text_item(&first_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&first_items, 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -846,7 +846,7 @@ while (true) {}
|
||||
r"(?s)\A",
|
||||
r"Script terminated\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&second_items, 0),
|
||||
text_item(&second_items, /*index*/ 0),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -907,8 +907,8 @@ text("session b done");
|
||||
let first_request = first_completion.single_request();
|
||||
let first_items = custom_tool_output_items(&first_request, "call-1");
|
||||
assert_eq!(first_items.len(), 2);
|
||||
let session_a_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, 1), "session a start");
|
||||
let session_a_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "session a start");
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -933,8 +933,8 @@ text("session b done");
|
||||
let second_request = second_completion.single_request();
|
||||
let second_items = custom_tool_output_items(&second_request, "call-2");
|
||||
assert_eq!(second_items.len(), 2);
|
||||
let session_b_id = extract_running_cell_id(text_item(&second_items, 0));
|
||||
assert_eq!(text_item(&second_items, 1), "session b start");
|
||||
let session_b_id = extract_running_cell_id(text_item(&second_items, /*index*/ 0));
|
||||
assert_eq!(text_item(&second_items, /*index*/ 1), "session b start");
|
||||
assert_ne!(session_a_id, session_b_id);
|
||||
|
||||
fs::write(&session_a_gate, "ready")?;
|
||||
@@ -973,9 +973,9 @@ text("session b done");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&third_items, 0),
|
||||
text_item(&third_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&third_items, 1), "session a done");
|
||||
assert_eq!(text_item(&third_items, /*index*/ 1), "session a done");
|
||||
|
||||
fs::write(&session_b_gate, "ready")?;
|
||||
responses::mount_sse_once(
|
||||
@@ -1013,9 +1013,9 @@ text("session b done");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&fourth_items, 0),
|
||||
text_item(&fourth_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&fourth_items, 1), "session b done");
|
||||
assert_eq!(text_item(&fourth_items, /*index*/ 1), "session b done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1065,8 +1065,8 @@ text("phase 2");
|
||||
let first_request = first_completion.single_request();
|
||||
let first_items = custom_tool_output_items(&first_request, "call-1");
|
||||
assert_eq!(first_items.len(), 2);
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "phase 1");
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -1103,7 +1103,7 @@ text("phase 2");
|
||||
r"(?s)\A",
|
||||
r"Script terminated\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&second_items, 0),
|
||||
text_item(&second_items, /*index*/ 0),
|
||||
);
|
||||
|
||||
responses::mount_sse_once(
|
||||
@@ -1140,9 +1140,9 @@ text("after terminate");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&third_items, 0),
|
||||
text_item(&third_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&third_items, 1), "after terminate");
|
||||
assert_eq!(text_item(&third_items, /*index*/ 1), "after terminate");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1197,10 +1197,10 @@ async fn code_mode_wait_returns_error_for_unknown_session() -> Result<()> {
|
||||
r"(?s)\A",
|
||||
r"Script failed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(
|
||||
text_item(&items, 1),
|
||||
text_item(&items, /*index*/ 1),
|
||||
"Script error:\nexec cell 999999 not found"
|
||||
);
|
||||
|
||||
@@ -1268,8 +1268,8 @@ text("session b done");
|
||||
let first_request = first_completion.single_request();
|
||||
let first_items = custom_tool_output_items(&first_request, "call-1");
|
||||
assert_eq!(first_items.len(), 2);
|
||||
let session_a_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, 1), "session a start");
|
||||
let session_a_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "session a start");
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -1294,8 +1294,8 @@ text("session b done");
|
||||
let second_request = second_completion.single_request();
|
||||
let second_items = custom_tool_output_items(&second_request, "call-2");
|
||||
assert_eq!(second_items.len(), 2);
|
||||
let session_b_id = extract_running_cell_id(text_item(&second_items, 0));
|
||||
assert_eq!(text_item(&second_items, 1), "session b start");
|
||||
let session_b_id = extract_running_cell_id(text_item(&second_items, /*index*/ 0));
|
||||
assert_eq!(text_item(&second_items, /*index*/ 1), "session b start");
|
||||
|
||||
fs::write(&session_a_gate, "ready")?;
|
||||
responses::mount_sse_once(
|
||||
@@ -1333,10 +1333,10 @@ text("session b done");
|
||||
r"(?s)\A",
|
||||
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&third_items, 0),
|
||||
text_item(&third_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(
|
||||
extract_running_cell_id(text_item(&third_items, 0)),
|
||||
extract_running_cell_id(text_item(&third_items, /*index*/ 0)),
|
||||
session_b_id
|
||||
);
|
||||
|
||||
@@ -1384,7 +1384,7 @@ text("session b done");
|
||||
r"(?s)\A",
|
||||
r"Script terminated\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&fourth_items, 0),
|
||||
text_item(&fourth_items, /*index*/ 0),
|
||||
);
|
||||
}
|
||||
2 => {
|
||||
@@ -1393,9 +1393,9 @@ text("session b done");
|
||||
r"(?s)\A",
|
||||
r"Script (?:completed|terminated)\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&fourth_items, 0),
|
||||
text_item(&fourth_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&fourth_items, 1), "session a done");
|
||||
assert_eq!(text_item(&fourth_items, /*index*/ 1), "session a done");
|
||||
}
|
||||
other => panic!("unexpected number of content items: {other}"),
|
||||
}
|
||||
@@ -1455,9 +1455,9 @@ text("after yield");
|
||||
r"(?s)\A",
|
||||
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&first_items, 0),
|
||||
text_item(&first_items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&first_items, 1), "before yield");
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "before yield");
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
@@ -1541,8 +1541,8 @@ text("token one token two token three token four token five token six token seve
|
||||
let first_request = first_completion.single_request();
|
||||
let first_items = custom_tool_output_items(&first_request, "call-1");
|
||||
assert_eq!(first_items.len(), 2);
|
||||
assert_eq!(text_item(&first_items, 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
|
||||
assert_eq!(text_item(&first_items, /*index*/ 1), "phase 1");
|
||||
let cell_id = extract_running_cell_id(text_item(&first_items, /*index*/ 0));
|
||||
|
||||
fs::write(&completion_gate, "ready")?;
|
||||
responses::mount_sse_once(
|
||||
@@ -1581,7 +1581,7 @@ text("token one token two token three token four token five token six token seve
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&second_items, 0),
|
||||
text_item(&second_items, /*index*/ 0),
|
||||
);
|
||||
let expected_pattern = r#"(?sx)
|
||||
\A
|
||||
@@ -1590,7 +1590,7 @@ Total\ output\ lines:\ 1\n
|
||||
.*…\d+\ tokens\ truncated….*
|
||||
\z
|
||||
"#;
|
||||
assert_regex_match(expected_pattern, text_item(&second_items, 1));
|
||||
assert_regex_match(expected_pattern, text_item(&second_items, /*index*/ 1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1606,7 +1606,7 @@ async fn code_mode_can_output_serialized_text_via_global_helper() -> Result<()>
|
||||
r#"
|
||||
text({ json: true });
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1639,7 +1639,7 @@ notify("code_mode_notify_marker");
|
||||
await tools.test_sync_tool({});
|
||||
text("done");
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1677,7 +1677,7 @@ text("before");
|
||||
exit();
|
||||
text("after");
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1695,9 +1695,9 @@ text("after");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&items, 1), "before");
|
||||
assert_eq!(text_item(&items, /*index*/ 1), "before");
|
||||
assert_eq!(output, "before");
|
||||
|
||||
Ok(())
|
||||
@@ -1716,7 +1716,7 @@ const circular = {};
|
||||
circular.self = circular;
|
||||
text(circular);
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1736,10 +1736,10 @@ text(circular);
|
||||
r"(?s)\A",
|
||||
r"Script failed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert!(text_item(&items, 1).contains("Script error:"));
|
||||
assert!(text_item(&items, 1).contains("Converting circular structure to JSON"));
|
||||
assert!(text_item(&items, /*index*/ 1).contains("Script error:"));
|
||||
assert!(text_item(&items, /*index*/ 1).contains("Converting circular structure to JSON"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1756,7 +1756,7 @@ async fn code_mode_can_output_images_via_global_helper() -> Result<()> {
|
||||
image("https://example.com/image.jpg");
|
||||
image("data:image/png;base64,AAA");
|
||||
"#,
|
||||
false,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1774,7 +1774,7 @@ image("data:image/png;base64,AAA");
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(
|
||||
items[1],
|
||||
@@ -1857,7 +1857,7 @@ image(out);
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1889,8 +1889,13 @@ async fn code_mode_can_apply_patch_via_nested_tool() -> Result<()> {
|
||||
);
|
||||
let code = format!("text(await tools.apply_patch({patch:?}));\n");
|
||||
|
||||
let (test, second_mock) =
|
||||
run_code_mode_turn(&server, "use exec to run apply_patch", &code, true).await?;
|
||||
let (test, second_mock) = run_code_mode_turn(
|
||||
&server,
|
||||
"use exec to run apply_patch",
|
||||
&code,
|
||||
/*include_apply_patch*/ true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let req = second_mock.single_request();
|
||||
let items = custom_tool_output_items(&req, "call-1");
|
||||
@@ -1908,9 +1913,9 @@ async fn code_mode_can_apply_patch_via_nested_tool() -> Result<()> {
|
||||
r"(?s)\A",
|
||||
r"Script completed\nWall time \d+\.\d seconds\nOutput:\n\z"
|
||||
),
|
||||
text_item(&items, 0),
|
||||
text_item(&items, /*index*/ 0),
|
||||
);
|
||||
assert_eq!(text_item(&items, 1), "{}");
|
||||
assert_eq!(text_item(&items, /*index*/ 1), "{}");
|
||||
|
||||
let file_path = test.cwd_path().join(file_name);
|
||||
assert_eq!(fs::read_to_string(&file_path)?, "hello from code_mode\n");
|
||||
@@ -2183,8 +2188,13 @@ const tool = ALL_TOOLS.find(({ name }) => name === "view_image");
|
||||
text(JSON.stringify(tool));
|
||||
"#;
|
||||
|
||||
let (_test, second_mock) =
|
||||
run_code_mode_turn(&server, "use exec to inspect ALL_TOOLS", code, false).await?;
|
||||
let (_test, second_mock) = run_code_mode_turn(
|
||||
&server,
|
||||
"use exec to inspect ALL_TOOLS",
|
||||
code,
|
||||
/*include_apply_patch*/ false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let req = second_mock.single_request();
|
||||
let (output, success) = custom_tool_output_body_and_success(&req, "call-1");
|
||||
@@ -2273,7 +2283,7 @@ async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
|
||||
}),
|
||||
defer_loading: true,
|
||||
}],
|
||||
false,
|
||||
/*persist_extended_history*/ false,
|
||||
)
|
||||
.await?;
|
||||
let mut test = base_test;
|
||||
|
||||
@@ -94,7 +94,8 @@ fn json_fragment(text: &str) -> String {
|
||||
}
|
||||
|
||||
fn non_openai_model_provider(server: &MockServer) -> ModelProviderInfo {
|
||||
let mut provider = built_in_model_providers(/* openai_base_url */ None)["openai"].clone();
|
||||
let mut provider =
|
||||
built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone();
|
||||
provider.name = "OpenAI (test)".into();
|
||||
provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
provider.supports_websockets = false;
|
||||
@@ -413,11 +414,11 @@ async fn manual_compact_uses_custom_prompt() {
|
||||
let server = start_mock_server().await;
|
||||
let first_turn = sse(vec![
|
||||
ev_assistant_message("m0", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r0", 80),
|
||||
ev_completed_with_tokens("r0", /*total_tokens*/ 80),
|
||||
]);
|
||||
let compact_turn = sse(vec![
|
||||
ev_assistant_message("m1", SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r1", 100),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 100),
|
||||
]);
|
||||
let request_log = mount_sse_sequence(&server, vec![first_turn, compact_turn]).await;
|
||||
|
||||
@@ -508,7 +509,7 @@ async fn manual_compact_emits_api_and_local_token_usage_events() {
|
||||
// history.
|
||||
let sse_compact = sse(vec![
|
||||
ev_assistant_message("m1", SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r1", 0),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 0),
|
||||
]);
|
||||
mount_sse_once(&server, sse_compact).await;
|
||||
|
||||
@@ -1211,21 +1212,21 @@ async fn auto_compact_runs_after_token_limit_hit() {
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 70_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
|
||||
]);
|
||||
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
ev_completed_with_tokens("r2", 330_000),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
|
||||
let sse3 = sse(vec![
|
||||
ev_assistant_message("m3", AUTO_SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r3", 200),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 200),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 120),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 120),
|
||||
]);
|
||||
let prefixed_auto_summary = AUTO_SUMMARY_TEXT;
|
||||
|
||||
@@ -1404,19 +1405,19 @@ async fn auto_compact_emits_context_compaction_items() {
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 70_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
|
||||
]);
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
ev_completed_with_tokens("r2", 330_000),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
let sse3 = sse(vec![
|
||||
ev_assistant_message("m3", AUTO_SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r3", 200),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 200),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 120),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 120),
|
||||
]);
|
||||
|
||||
mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
@@ -1487,19 +1488,19 @@ async fn auto_compact_starts_after_turn_started() {
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 70_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
|
||||
]);
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
ev_completed_with_tokens("r2", 330_000),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
let sse3 = sse(vec![
|
||||
ev_assistant_message("m3", AUTO_SUMMARY_TEXT),
|
||||
ev_completed_with_tokens("r3", 200),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 200),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 120),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 120),
|
||||
]);
|
||||
|
||||
mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
@@ -1704,8 +1705,8 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![
|
||||
model_info_with_context_window(previous_model, 273_000),
|
||||
model_info_with_context_window(next_model, 125_000),
|
||||
model_info_with_context_window(previous_model, /*context_window*/ 273_000),
|
||||
model_info_with_context_window(next_model, /*context_window*/ 125_000),
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -1716,15 +1717,15 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_assistant_message("m1", "before switch"),
|
||||
ev_completed_with_tokens("r1", 120_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 120_000),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m2", "PRE_SAMPLING_SUMMARY"),
|
||||
ev_completed_with_tokens("r2", 10),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 10),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m3", "after switch"),
|
||||
ev_completed_with_tokens("r3", 100),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 100),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1830,8 +1831,8 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![
|
||||
model_info_with_context_window(previous_model, 273_000),
|
||||
model_info_with_context_window(next_model, 125_000),
|
||||
model_info_with_context_window(previous_model, /*context_window*/ 273_000),
|
||||
model_info_with_context_window(next_model, /*context_window*/ 125_000),
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -1842,15 +1843,15 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_assistant_message("m1", "before resume"),
|
||||
ev_completed_with_tokens("r1", 120_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 120_000),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m2", "PRE_SAMPLING_SUMMARY"),
|
||||
ev_completed_with_tokens("r2", 10),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 10),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m3", "after resume"),
|
||||
ev_completed_with_tokens("r3", 100),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 100),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1971,22 +1972,22 @@ async fn auto_compact_persists_rollout_entries() {
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 70_000),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 70_000),
|
||||
]);
|
||||
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
ev_completed_with_tokens("r2", 330_000),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 330_000),
|
||||
]);
|
||||
|
||||
let auto_summary_payload = auto_summary(AUTO_SUMMARY_TEXT);
|
||||
let sse3 = sse(vec![
|
||||
ev_assistant_message("m3", &auto_summary_payload),
|
||||
ev_completed_with_tokens("r3", 200),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 200),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 120),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 120),
|
||||
]);
|
||||
|
||||
let first_matcher = |req: &wiremock::Request| {
|
||||
@@ -2492,29 +2493,29 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 500),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]);
|
||||
let first_summary_payload = auto_summary(FIRST_AUTO_SUMMARY);
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", &first_summary_payload),
|
||||
ev_completed_with_tokens("r2", 50),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 50),
|
||||
]);
|
||||
let sse3 = sse(vec![
|
||||
ev_function_call(DUMMY_CALL_ID, DUMMY_FUNCTION_NAME, "{}"),
|
||||
ev_completed_with_tokens("r3", 150),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 150),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", SECOND_LARGE_REPLY),
|
||||
ev_completed_with_tokens("r4", 450),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 450),
|
||||
]);
|
||||
let second_summary_payload = auto_summary(SECOND_AUTO_SUMMARY);
|
||||
let sse5 = sse(vec![
|
||||
ev_assistant_message("m5", &second_summary_payload),
|
||||
ev_completed_with_tokens("r5", 60),
|
||||
ev_completed_with_tokens("r5", /*total_tokens*/ 60),
|
||||
]);
|
||||
let sse6 = sse(vec![
|
||||
ev_assistant_message("m6", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r6", 120),
|
||||
ev_completed_with_tokens("r6", /*total_tokens*/ 120),
|
||||
]);
|
||||
let follow_up_user = "FOLLOW_UP_AUTO_COMPACT";
|
||||
let final_user = "FINAL_AUTO_COMPACT";
|
||||
@@ -2612,11 +2613,11 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
|
||||
let auto_summary_payload = auto_summary(AUTO_SUMMARY_TEXT);
|
||||
let auto_compact_turn = sse(vec![
|
||||
ev_assistant_message("m2", &auto_summary_payload),
|
||||
ev_completed_with_tokens("r3", 10),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 10),
|
||||
]);
|
||||
let post_auto_compact_turn = sse(vec![
|
||||
ev_assistant_message("m3", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 10),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 10),
|
||||
]);
|
||||
|
||||
// Mount responses in order and keep mocks only for the ones we assert on.
|
||||
@@ -2716,9 +2717,11 @@ async fn auto_compact_clamps_config_limit_to_context_window() {
|
||||
let auto_summary_payload = auto_summary(AUTO_SUMMARY_TEXT);
|
||||
let auto_compact_turn = sse(vec![
|
||||
ev_assistant_message("m2", &auto_summary_payload),
|
||||
ev_completed_with_tokens("r2", 10),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 10),
|
||||
]);
|
||||
let post_auto_compact_turn = sse(vec![ev_completed_with_tokens("r3", 10)]);
|
||||
let post_auto_compact_turn = sse(vec![ev_completed_with_tokens(
|
||||
"r3", /*total_tokens*/ 10,
|
||||
)]);
|
||||
|
||||
let first_turn_mock = mount_sse_once(&server, first_turn).await;
|
||||
let auto_compact_mock = mount_sse_once(&server, auto_compact_turn).await;
|
||||
@@ -2772,15 +2775,15 @@ async fn auto_compact_counts_encrypted_reasoning_before_last_user() {
|
||||
|
||||
let first_turn = sse(vec![
|
||||
ev_reasoning_item("pre-reasoning", &["pre"], &[&pre_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r1", 10),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 10),
|
||||
]);
|
||||
let second_turn = sse(vec![
|
||||
ev_reasoning_item("post-reasoning", &["post"], &[&post_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r2", 80),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]);
|
||||
let third_turn = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 1),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 1),
|
||||
]);
|
||||
|
||||
let request_log = mount_sse_sequence(
|
||||
@@ -2898,15 +2901,15 @@ async fn auto_compact_runs_when_reasoning_header_clears_between_turns() {
|
||||
|
||||
let first_turn = sse(vec![
|
||||
ev_reasoning_item("pre-reasoning", &["pre"], &[&pre_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r1", 10),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 10),
|
||||
]);
|
||||
let second_turn = sse(vec![
|
||||
ev_reasoning_item("post-reasoning", &["post"], &[&post_last_reasoning_content]),
|
||||
ev_completed_with_tokens("r2", 80),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]);
|
||||
let third_turn = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 1),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 1),
|
||||
]);
|
||||
|
||||
let responses = vec![
|
||||
@@ -2975,19 +2978,19 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
|
||||
|
||||
let sse1 = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 60),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 60),
|
||||
]);
|
||||
let sse2 = sse(vec![
|
||||
ev_assistant_message("m2", "SECOND_REPLY"),
|
||||
ev_completed_with_tokens("r2", 500),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 500),
|
||||
]);
|
||||
let sse3 = sse(vec![
|
||||
ev_assistant_message("m3", "PRE_TURN_SUMMARY"),
|
||||
ev_completed_with_tokens("r3", 100),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 100),
|
||||
]);
|
||||
let sse4 = sse(vec![
|
||||
ev_assistant_message("m4", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r4", 80),
|
||||
ev_completed_with_tokens("r4", /*total_tokens*/ 80),
|
||||
]);
|
||||
let request_log = mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
|
||||
@@ -3100,15 +3103,15 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_assistant_message("m1", "BEFORE_SWITCH_REPLY"),
|
||||
ev_completed_with_tokens("r1", 500),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m2", "PRETURN_SWITCH_SUMMARY"),
|
||||
ev_completed_with_tokens("r2", 100),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 100),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_assistant_message("m3", "AFTER_SWITCH_REPLY"),
|
||||
ev_completed_with_tokens("r3", 100),
|
||||
ev_completed_with_tokens("r3", /*total_tokens*/ 100),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -3222,7 +3225,7 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
|
||||
|
||||
let first_turn = sse(vec![
|
||||
ev_assistant_message("m1", FIRST_REPLY),
|
||||
ev_completed_with_tokens("r1", 500),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]);
|
||||
let mut responses = vec![first_turn];
|
||||
responses.extend(
|
||||
@@ -3309,11 +3312,11 @@ async fn snapshot_request_shape_manual_compact_without_previous_user_messages()
|
||||
|
||||
let compact_turn = sse(vec![
|
||||
ev_assistant_message("m1", "MANUAL_EMPTY_SUMMARY"),
|
||||
ev_completed_with_tokens("r1", 90),
|
||||
ev_completed_with_tokens("r1", /*total_tokens*/ 90),
|
||||
]);
|
||||
let follow_up_turn = sse(vec![
|
||||
ev_assistant_message("m2", FINAL_REPLY),
|
||||
ev_completed_with_tokens("r2", 80),
|
||||
ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]);
|
||||
let request_log = mount_sse_sequence(&server, vec![compact_turn, follow_up_turn]).await;
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ async fn remote_compact_runs_automatically() -> Result<()> {
|
||||
harness.server(),
|
||||
sse(vec![
|
||||
responses::ev_shell_command_call("m1", "echo 'hi'"),
|
||||
responses::ev_completed_with_tokens("resp-1", 100000000), // over token limit
|
||||
responses::ev_completed_with_tokens("resp-1", /*total_tokens*/ 100000000), // over token limit
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -548,7 +548,10 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window()
|
||||
vec![
|
||||
sse(vec![
|
||||
responses::ev_shell_command_call(retained_call_id, retained_command),
|
||||
responses::ev_completed_with_tokens("retained-call-response", 100),
|
||||
responses::ev_completed_with_tokens(
|
||||
"retained-call-response",
|
||||
/*total_tokens*/ 100,
|
||||
),
|
||||
]),
|
||||
sse(vec![
|
||||
responses::ev_assistant_message("retained-assistant", "retained complete"),
|
||||
@@ -556,11 +559,14 @@ async fn auto_remote_compact_trims_function_call_history_to_fit_context_window()
|
||||
]),
|
||||
sse(vec![
|
||||
responses::ev_shell_command_call(trimmed_call_id, trimmed_command),
|
||||
responses::ev_completed_with_tokens("trimmed-call-response", 100),
|
||||
responses::ev_completed_with_tokens(
|
||||
"trimmed-call-response",
|
||||
/*total_tokens*/ 100,
|
||||
),
|
||||
]),
|
||||
sse(vec![responses::ev_completed_with_tokens(
|
||||
"trimmed-final-response",
|
||||
500_000,
|
||||
/*total_tokens*/ 500_000,
|
||||
)]),
|
||||
],
|
||||
)
|
||||
@@ -672,7 +678,7 @@ async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()> {
|
||||
harness.server(),
|
||||
sse(vec![
|
||||
responses::ev_assistant_message("initial-assistant", "initial turn complete"),
|
||||
responses::ev_completed_with_tokens("initial-response", 500_000),
|
||||
responses::ev_completed_with_tokens("initial-response", /*total_tokens*/ 500_000),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -1468,11 +1474,11 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_sta
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1601,11 +1607,11 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1686,11 +1692,11 @@ async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start()
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 60),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 60),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1776,15 +1782,15 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_real
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("setup", "REMOTE_SETUP_REPLY"),
|
||||
responses::ev_completed_with_tokens("setup-response", 60),
|
||||
responses::ev_completed_with_tokens("setup-response", /*total_tokens*/ 60),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_function_call("call-remote-mid-turn", DUMMY_FUNCTION_NAME, "{}"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_MID_TURN_FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1880,11 +1886,11 @@ async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() ->
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 60),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 60),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_AFTER_RESUME_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -1984,15 +1990,15 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 60),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 60),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_SECOND_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 500),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 500),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m3", "REMOTE_FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r3", 80),
|
||||
responses::ev_completed_with_tokens("r3", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -2088,7 +2094,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "BEFORE_SWITCH_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2096,7 +2102,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "AFTER_SWITCH_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2224,7 +2230,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
|
||||
harness.server(),
|
||||
vec![responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_FIRST_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
])],
|
||||
)
|
||||
.await;
|
||||
@@ -2243,7 +2249,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceed
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_POST_COMPACT_SHOULD_NOT_RUN"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2325,11 +2331,11 @@ async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Res
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_function_call("call-remote-mid-turn", DUMMY_FUNCTION_NAME, "{}"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_MID_TURN_FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
],
|
||||
)
|
||||
@@ -2394,7 +2400,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_function_call("call-remote-summary-only", DUMMY_FUNCTION_NAME, "{}"),
|
||||
responses::ev_completed_with_tokens("r1", 500),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 500),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2402,7 +2408,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinject
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m2", "REMOTE_SUMMARY_ONLY_FINAL_REPLY"),
|
||||
responses::ev_completed_with_tokens("r2", 80),
|
||||
responses::ev_completed_with_tokens("r2", /*total_tokens*/ 80),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2477,7 +2483,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("setup", "REMOTE_SETUP_REPLY"),
|
||||
responses::ev_completed_with_tokens("setup-response", 60),
|
||||
responses::ev_completed_with_tokens("setup-response", /*total_tokens*/ 60),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2485,7 +2491,7 @@ async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjec
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_shell_command_call("call-remote-multi-summary", "echo multi-summary"),
|
||||
responses::ev_completed_with_tokens("r1", 1_000),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 1_000),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
@@ -2580,7 +2586,7 @@ async fn snapshot_request_shape_remote_manual_compact_without_previous_user_mess
|
||||
harness.server(),
|
||||
responses::sse(vec![
|
||||
responses::ev_assistant_message("m1", "REMOTE_MANUAL_EMPTY_FOLLOW_UP_REPLY"),
|
||||
responses::ev_completed_with_tokens("r1", 80),
|
||||
responses::ev_completed_with_tokens("r1", /*total_tokens*/ 80),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -180,7 +180,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
|
||||
"compact+resume test expects resumed path {resumed_path:?} to exist",
|
||||
);
|
||||
|
||||
let forked = fork_thread(&manager, &config, resumed_path, 2).await;
|
||||
let forked = fork_thread(&manager, &config, resumed_path, /*nth_user_message*/ 2).await;
|
||||
user_turn(&forked, "AFTER_FORK").await;
|
||||
|
||||
// 3. Capture the requests to the model and validate the history slices.
|
||||
@@ -316,7 +316,7 @@ async fn compact_resume_after_second_compaction_preserves_history() -> Result<()
|
||||
let request_log = mount_second_compact_sequence(&server).await;
|
||||
|
||||
// 2. Drive the conversation through compact -> resume -> fork -> compact -> resume.
|
||||
let (_home, config, manager, base) = start_test_conversation(&server, None).await;
|
||||
let (_home, config, manager, base) = start_test_conversation(&server, /*model*/ None).await;
|
||||
|
||||
user_turn(&base, "hello world").await;
|
||||
compact_conversation(&base).await;
|
||||
@@ -335,7 +335,7 @@ async fn compact_resume_after_second_compaction_preserves_history() -> Result<()
|
||||
"second compact test expects resumed path {resumed_path:?} to exist",
|
||||
);
|
||||
|
||||
let forked = fork_thread(&manager, &config, resumed_path, 3).await;
|
||||
let forked = fork_thread(&manager, &config, resumed_path, /*nth_user_message*/ 3).await;
|
||||
user_turn(&forked, "AFTER_FORK").await;
|
||||
|
||||
compact_conversation(&forked).await;
|
||||
@@ -468,7 +468,7 @@ async fn snapshot_rollback_past_compaction_replays_append_only_history() -> Resu
|
||||
|
||||
let request_log = mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await;
|
||||
|
||||
let (_home, _config, _manager, base) = start_test_conversation(&server, None).await;
|
||||
let (_home, _config, _manager, base) = start_test_conversation(&server, /*model*/ None).await;
|
||||
|
||||
user_turn(&base, "hello world").await;
|
||||
compact_conversation(&base).await;
|
||||
@@ -845,10 +845,15 @@ async fn resume_conversation(
|
||||
let auth_manager = codex_core::test_support::auth_manager_from_auth(
|
||||
codex_core::CodexAuth::from_api_key("dummy"),
|
||||
);
|
||||
Box::pin(manager.resume_thread_from_rollout(config.clone(), path, auth_manager, None))
|
||||
.await
|
||||
.expect("resume conversation")
|
||||
.thread
|
||||
Box::pin(manager.resume_thread_from_rollout(
|
||||
config.clone(),
|
||||
path,
|
||||
auth_manager,
|
||||
/*parent_trace*/ None,
|
||||
))
|
||||
.await
|
||||
.expect("resume conversation")
|
||||
.thread
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -858,8 +863,14 @@ async fn fork_thread(
|
||||
path: std::path::PathBuf,
|
||||
nth_user_message: usize,
|
||||
) -> Arc<CodexThread> {
|
||||
Box::pin(manager.fork_thread(nth_user_message, config.clone(), path, false, None))
|
||||
.await
|
||||
.expect("fork conversation")
|
||||
.thread
|
||||
Box::pin(manager.fork_thread(
|
||||
nth_user_message,
|
||||
config.clone(),
|
||||
path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
))
|
||||
.await
|
||||
.expect("fork conversation")
|
||||
.thread
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ fn skip_test() -> bool {
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput> {
|
||||
let sandbox_type = get_platform_sandbox(false).expect("should be able to get sandbox type");
|
||||
let sandbox_type = get_platform_sandbox(/*windows_sandbox_enabled*/ false)
|
||||
.expect("should be able to get sandbox type");
|
||||
assert_eq!(sandbox_type, SandboxType::MacosSeatbelt);
|
||||
|
||||
let params = ExecParams {
|
||||
@@ -56,8 +57,8 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
|
||||
NetworkSandboxPolicy::from(&policy),
|
||||
tmp.path(),
|
||||
&None,
|
||||
false,
|
||||
None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1321,7 +1321,8 @@ async fn pre_tool_use_does_not_fire_for_non_shell_tools() -> Result<()> {
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_pre_build_hook(|home| {
|
||||
if let Err(error) = write_pre_tool_use_hook(home, None, "json_deny", "should not fire")
|
||||
if let Err(error) =
|
||||
write_pre_tool_use_hook(home, /*matcher*/ None, "json_deny", "should not fire")
|
||||
{
|
||||
panic!("failed to write pre tool use hook test fixture: {error}");
|
||||
}
|
||||
@@ -1775,9 +1776,12 @@ async fn post_tool_use_does_not_fire_for_non_shell_tools() -> Result<()> {
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_pre_build_hook(|home| {
|
||||
if let Err(error) =
|
||||
write_post_tool_use_hook(home, None, "decision_block", "should not fire")
|
||||
{
|
||||
if let Err(error) = write_post_tool_use_hook(
|
||||
home,
|
||||
/*matcher*/ None,
|
||||
"decision_block",
|
||||
"should not fire",
|
||||
) {
|
||||
panic!("failed to write post tool use hook test fixture: {error}");
|
||||
}
|
||||
})
|
||||
|
||||
@@ -148,7 +148,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
|
||||
role: "user".to_string(),
|
||||
content: vec![
|
||||
ContentItem::InputText {
|
||||
text: codex_protocol::models::local_image_open_tag_text(1),
|
||||
text: codex_protocol::models::local_image_open_tag_text(/*label_number*/ 1),
|
||||
},
|
||||
ContentItem::InputImage { image_url },
|
||||
ContentItem::InputText {
|
||||
|
||||
@@ -253,7 +253,9 @@ async fn web_search_pollution_moves_selected_thread_into_removed_phase2_inputs()
|
||||
.resume(&server, home.clone(), rollout_path.clone())
|
||||
.await?;
|
||||
|
||||
let first_phase2_request = wait_for_request(&responses, 1).await.remove(0);
|
||||
let first_phase2_request = wait_for_request(&responses, /*expected_count*/ 1)
|
||||
.await
|
||||
.remove(0);
|
||||
let first_phase2_prompt = phase2_prompt_text(&first_phase2_request);
|
||||
assert!(
|
||||
first_phase2_prompt.contains("- selected inputs this run: 1"),
|
||||
@@ -295,7 +297,9 @@ async fn web_search_pollution_moves_selected_thread_into_removed_phase2_inputs()
|
||||
let selection = {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
let selection = db.get_phase2_input_selection(1, 30).await?;
|
||||
let selection = db
|
||||
.get_phase2_input_selection(/*n*/ 1, /*max_unused_days*/ 30)
|
||||
.await?;
|
||||
if selection.selected.is_empty()
|
||||
&& selection.retained_thread_ids.is_empty()
|
||||
&& selection.removed.len() == 1
|
||||
@@ -340,7 +344,7 @@ async fn build_test_codex(server: &wiremock::MockServer, home: Arc<TempDir>) ->
|
||||
async fn init_state_db(home: &Arc<TempDir>) -> Result<Arc<codex_state::StateRuntime>> {
|
||||
let db =
|
||||
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".into()).await?;
|
||||
db.mark_backfill_complete(None).await?;
|
||||
db.mark_backfill_complete(/*last_watermark*/ None).await?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -379,7 +383,7 @@ async fn seed_stage1_output(
|
||||
}
|
||||
|
||||
async fn wait_for_single_request(mock: &ResponseMock) -> ResponsesRequest {
|
||||
wait_for_request(mock, 1).await.remove(0)
|
||||
wait_for_request(mock, /*expected_count*/ 1).await.remove(0)
|
||||
}
|
||||
|
||||
async fn wait_for_request(mock: &ResponseMock, expected_count: usize) -> Vec<ResponsesRequest> {
|
||||
@@ -412,7 +416,9 @@ async fn wait_for_phase2_success(
|
||||
) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
let selection = db.get_phase2_input_selection(1, 30).await?;
|
||||
let selection = db
|
||||
.get_phase2_input_selection(/*n*/ 1, /*max_unused_days*/ 30)
|
||||
.await?;
|
||||
if selection.selected.len() == 1
|
||||
&& selection.selected[0].thread_id == expected_thread_id
|
||||
&& selection.retained_thread_ids == vec![expected_thread_id]
|
||||
@@ -439,7 +445,10 @@ async fn seed_stage1_output_for_existing_thread(
|
||||
) -> Result<()> {
|
||||
let owner = ThreadId::new();
|
||||
let claim = db
|
||||
.try_claim_stage1_job(thread_id, owner, updated_at, 3_600, 64)
|
||||
.try_claim_stage1_job(
|
||||
thread_id, owner, updated_at, /*lease_seconds*/ 3_600,
|
||||
/*max_running_jobs*/ 64,
|
||||
)
|
||||
.await?;
|
||||
let ownership_token = match claim {
|
||||
codex_state::Stage1JobClaimOutcome::Claimed { ownership_token } => ownership_token,
|
||||
|
||||
@@ -16,7 +16,7 @@ async fn offline_model_info_without_tool_output_override() {
|
||||
let manager = ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager,
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ async fn offline_model_info_without_tool_output_override() {
|
||||
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::bytes(10_000)
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ async fn offline_model_info_with_tool_output_override() {
|
||||
let manager = ModelsManager::new(
|
||||
config.codex_home.clone(),
|
||||
auth_manager,
|
||||
None,
|
||||
/*model_catalog*/ None,
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
|
||||
@@ -47,6 +47,6 @@ async fn offline_model_info_with_tool_output_override() {
|
||||
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::tokens(123)
|
||||
TruncationPolicyConfig::tokens(/*limit*/ 123)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ fn test_model_info(
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(272_000),
|
||||
@@ -308,7 +308,7 @@ async fn service_tier_change_is_applied_on_next_http_turn() -> Result<()> {
|
||||
|
||||
test.submit_turn_with_service_tier("fast turn", Some(ServiceTier::Fast))
|
||||
.await?;
|
||||
test.submit_turn_with_service_tier("standard turn", None)
|
||||
test.submit_turn_with_service_tier("standard turn", /*service_tier*/ None)
|
||||
.await?;
|
||||
|
||||
let requests = resp_mock.requests();
|
||||
@@ -498,7 +498,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_image_generation_call("ig_123", "completed", "lobster", "Zm9v"),
|
||||
ev_completed_with_tokens("resp-1", 10),
|
||||
ev_completed_with_tokens("resp-1", /*total_tokens*/ 10),
|
||||
]),
|
||||
sse_completed("resp-2"),
|
||||
],
|
||||
@@ -630,7 +630,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_image_generation_call("ig_123", "completed", "lobster", "Zm9v"),
|
||||
ev_completed_with_tokens("resp-1", 10),
|
||||
ev_completed_with_tokens("resp-1", /*total_tokens*/ 10),
|
||||
]),
|
||||
sse_completed("resp-2"),
|
||||
],
|
||||
@@ -764,7 +764,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_image_generation_call("ig_rollback", "completed", "lobster", "Zm9v"),
|
||||
ev_completed_with_tokens("resp-1", 10),
|
||||
ev_completed_with_tokens("resp-1", /*total_tokens*/ 10),
|
||||
]),
|
||||
sse_completed("resp-2"),
|
||||
],
|
||||
@@ -908,7 +908,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(large_context_window),
|
||||
@@ -935,11 +935,11 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_completed_with_tokens("resp-1", 100),
|
||||
ev_completed_with_tokens("resp-1", /*total_tokens*/ 100),
|
||||
]),
|
||||
sse(vec![
|
||||
ev_response_created("resp-2"),
|
||||
ev_completed_with_tokens("resp-2", 120),
|
||||
ev_completed_with_tokens("resp-2", /*total_tokens*/ 120),
|
||||
]),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ const DIFFERENT_VERSION_MODEL: &str = "codex-test-different-version";
|
||||
async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
|
||||
let remote_model = test_remote_model(REMOTE_MODEL, 1);
|
||||
let remote_model = test_remote_model(REMOTE_MODEL, /*priority*/ 1);
|
||||
let models_mock = responses::mount_models_once_with_etag(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -138,11 +138,11 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn uses_cache_when_version_matches() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let cached_model = test_remote_model(VERSIONED_MODEL, 1);
|
||||
let cached_model = test_remote_model(VERSIONED_MODEL, /*priority*/ 1);
|
||||
let models_mock = responses::mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![test_remote_model("remote", 2)],
|
||||
models: vec![test_remote_model("remote", /*priority*/ 2)],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -185,11 +185,11 @@ async fn uses_cache_when_version_matches() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn refreshes_when_cache_version_missing() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let cached_model = test_remote_model(MISSING_VERSION_MODEL, 1);
|
||||
let cached_model = test_remote_model(MISSING_VERSION_MODEL, /*priority*/ 1);
|
||||
let models_mock = responses::mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![test_remote_model("remote-missing", 2)],
|
||||
models: vec![test_remote_model("remote-missing", /*priority*/ 2)],
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -232,9 +232,9 @@ async fn refreshes_when_cache_version_missing() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn refreshes_when_cache_version_differs() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let cached_model = test_remote_model(DIFFERENT_VERSION_MODEL, 1);
|
||||
let cached_model = test_remote_model(DIFFERENT_VERSION_MODEL, /*priority*/ 1);
|
||||
let models_response = ModelsResponse {
|
||||
models: vec![test_remote_model("remote-different", 2)],
|
||||
models: vec![test_remote_model("remote-different", /*priority*/ 2)],
|
||||
};
|
||||
let mut models_mocks = Vec::new();
|
||||
for _ in 0..3 {
|
||||
@@ -344,7 +344,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo {
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(272_000),
|
||||
|
||||
@@ -503,8 +503,8 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
||||
test.config.approvals_reviewer,
|
||||
&Policy::empty(),
|
||||
test.config.cwd.as_path(),
|
||||
false,
|
||||
false,
|
||||
/*exec_permission_approvals_enabled*/ false,
|
||||
/*request_permissions_tool_enabled*/ false,
|
||||
)
|
||||
.into_text();
|
||||
// Normalize line endings to handle Windows vs Unix differences
|
||||
|
||||
@@ -661,7 +661,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(128_000),
|
||||
@@ -777,7 +777,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(128_000),
|
||||
|
||||
@@ -172,7 +172,11 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
|
||||
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await);
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -310,7 +314,11 @@ async fn conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth() ->
|
||||
|
||||
let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await);
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -365,7 +373,11 @@ async fn conversation_transport_close_emits_closed_event() -> Result<()> {
|
||||
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await);
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -572,7 +584,11 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
|
||||
.await;
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await);
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -672,7 +688,7 @@ async fn conversation_uses_experimental_realtime_ws_base_url_override() -> Resul
|
||||
let test = builder.build_with_websocket_server(&startup_server).await?;
|
||||
assert!(
|
||||
startup_server
|
||||
.wait_for_handshakes(1, Duration::from_secs(2))
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
@@ -724,7 +740,11 @@ async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() ->
|
||||
config.experimental_realtime_ws_backend_prompt = Some("prompt from config".to_string());
|
||||
});
|
||||
let test = builder.build_with_websocket_server(&server).await?;
|
||||
assert!(server.wait_for_handshakes(1, Duration::from_secs(2)).await);
|
||||
assert!(
|
||||
server
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
test.codex
|
||||
.submit(Op::RealtimeConversationStart(ConversationStartParams {
|
||||
@@ -784,7 +804,7 @@ async fn conversation_uses_experimental_realtime_ws_startup_context_override() -
|
||||
fs::write(test.workspace_path("README.md"), "workspace marker")?;
|
||||
assert!(
|
||||
startup_server
|
||||
.wait_for_handshakes(1, Duration::from_secs(2))
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
@@ -844,7 +864,7 @@ async fn conversation_disables_realtime_startup_context_with_empty_override() ->
|
||||
fs::write(test.workspace_path("README.md"), "workspace marker")?;
|
||||
assert!(
|
||||
startup_server
|
||||
.wait_for_handshakes(1, Duration::from_secs(2))
|
||||
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
|
||||
.await
|
||||
);
|
||||
|
||||
@@ -1238,7 +1258,9 @@ async fn conversation_handoff_persists_across_item_done_until_turn_complete() ->
|
||||
})
|
||||
.await;
|
||||
|
||||
let first_append = realtime_server.wait_for_request(0, 1).await;
|
||||
let first_append = realtime_server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 1)
|
||||
.await;
|
||||
assert_eq!(
|
||||
first_append.body_json()["type"].as_str(),
|
||||
Some("conversation.handoff.append")
|
||||
@@ -1262,7 +1284,9 @@ async fn conversation_handoff_persists_across_item_done_until_turn_complete() ->
|
||||
|
||||
let _ = gate_second_message_tx.send(());
|
||||
|
||||
let second_append = realtime_server.wait_for_request(0, 2).await;
|
||||
let second_append = realtime_server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 2)
|
||||
.await;
|
||||
assert_eq!(
|
||||
second_append.body_json()["type"].as_str(),
|
||||
Some("conversation.handoff.append")
|
||||
@@ -1777,7 +1801,9 @@ async fn delegated_turn_user_role_echo_does_not_redelegate_and_still_forwards_au
|
||||
"delegate now"
|
||||
);
|
||||
|
||||
let mirrored_request = realtime_server.wait_for_request(0, 1).await;
|
||||
let mirrored_request = realtime_server
|
||||
.wait_for_request(/*connection_index*/ 0, /*request_index*/ 1)
|
||||
.await;
|
||||
let mirrored_request_body = mirrored_request.body_json();
|
||||
eprintln!(
|
||||
"[realtime test +{}ms] saw mirrored request type={:?} handoff_id={:?} text={:?}",
|
||||
|
||||
@@ -62,14 +62,14 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<(
|
||||
let generic = test_remote_model_with_policy(
|
||||
"gpt-5.3",
|
||||
ModelVisibility::List,
|
||||
1_000,
|
||||
TruncationPolicyConfig::bytes(10_000),
|
||||
/*priority*/ 1_000,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
);
|
||||
let specific = test_remote_model_with_policy(
|
||||
"gpt-5.3-codex",
|
||||
ModelVisibility::List,
|
||||
1_000,
|
||||
TruncationPolicyConfig::bytes(10_000),
|
||||
/*priority*/ 1_000,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
);
|
||||
let specific = ModelInfo {
|
||||
display_name: "GPT 5.3 Codex".to_string(),
|
||||
@@ -95,7 +95,7 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<(
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -124,8 +124,8 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<(
|
||||
let mut remote_model = test_remote_model_with_policy(
|
||||
prefix_model,
|
||||
ModelVisibility::List,
|
||||
1_000,
|
||||
TruncationPolicyConfig::bytes(10_000),
|
||||
/*priority*/ 1_000,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
);
|
||||
remote_model.default_reasoning_level = Some(ReasoningEffort::High);
|
||||
remote_model.supported_reasoning_levels = vec![
|
||||
@@ -304,7 +304,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(272_000),
|
||||
@@ -434,8 +434,8 @@ async fn remote_models_truncation_policy_without_override_preserves_remote() ->
|
||||
let remote_model = test_remote_model_with_policy(
|
||||
slug,
|
||||
ModelVisibility::List,
|
||||
1,
|
||||
TruncationPolicyConfig::bytes(12_000),
|
||||
/*priority*/ 1,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 12_000),
|
||||
);
|
||||
mount_models_once(
|
||||
&server,
|
||||
@@ -458,7 +458,7 @@ async fn remote_models_truncation_policy_without_override_preserves_remote() ->
|
||||
let model_info = models_manager.get_model_info(slug, &test.config).await;
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::bytes(12_000)
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 12_000)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -478,8 +478,8 @@ async fn remote_models_truncation_policy_with_tool_output_override() -> Result<(
|
||||
let remote_model = test_remote_model_with_policy(
|
||||
slug,
|
||||
ModelVisibility::List,
|
||||
1,
|
||||
TruncationPolicyConfig::bytes(10_000),
|
||||
/*priority*/ 1,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
);
|
||||
mount_models_once(
|
||||
&server,
|
||||
@@ -503,7 +503,7 @@ async fn remote_models_truncation_policy_with_tool_output_override() -> Result<(
|
||||
let model_info = models_manager.get_model_info(slug, &test.config).await;
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::bytes(200)
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 200)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -548,7 +548,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(272_000),
|
||||
@@ -642,7 +642,8 @@ async fn remote_models_do_not_append_removed_builtin_presets() -> Result<()> {
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let remote_model = test_remote_model("remote-alpha", ModelVisibility::List, 0);
|
||||
let remote_model =
|
||||
test_remote_model("remote-alpha", ModelVisibility::List, /*priority*/ 0);
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -656,7 +657,7 @@ async fn remote_models_do_not_append_removed_builtin_presets() -> Result<()> {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -699,7 +700,11 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> {
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let remote_model = test_remote_model("remote-top", ModelVisibility::List, -10_000);
|
||||
let remote_model = test_remote_model(
|
||||
"remote-top",
|
||||
ModelVisibility::List,
|
||||
/*priority*/ -10_000,
|
||||
);
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -713,7 +718,7 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -744,7 +749,7 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> {
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let slug = bundled_model_slug();
|
||||
let mut remote_model = test_remote_model(&slug, ModelVisibility::List, 0);
|
||||
let mut remote_model = test_remote_model(&slug, ModelVisibility::List, /*priority*/ 0);
|
||||
remote_model.display_name = "Overridden".to_string();
|
||||
remote_model.description = Some("Overridden description".to_string());
|
||||
let models_mock = mount_models_once(
|
||||
@@ -760,7 +765,7 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -804,7 +809,7 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -830,7 +835,8 @@ async fn remote_models_request_times_out_after_5s() -> Result<()> {
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let remote_model = test_remote_model("remote-timeout", ModelVisibility::List, 0);
|
||||
let remote_model =
|
||||
test_remote_model("remote-timeout", ModelVisibility::List, /*priority*/ 0);
|
||||
let models_mock = mount_models_once_with_delay(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -845,7 +851,7 @@ async fn remote_models_request_times_out_after_5s() -> Result<()> {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -897,7 +903,11 @@ async fn remote_models_hide_picker_only_models() -> Result<()> {
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let remote_model = test_remote_model("codex-auto-balanced", ModelVisibility::Hide, 0);
|
||||
let remote_model = test_remote_model(
|
||||
"codex-auto-balanced",
|
||||
ModelVisibility::Hide,
|
||||
/*priority*/ 0,
|
||||
);
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
@@ -911,7 +921,7 @@ async fn remote_models_hide_picker_only_models() -> Result<()> {
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let provider = ModelProviderInfo {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
..built_in_model_providers(/* openai_base_url */ None)["openai"].clone()
|
||||
..built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None)["openai"].clone()
|
||||
};
|
||||
let manager = codex_core::test_support::models_manager_with_provider(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -982,7 +992,7 @@ fn test_remote_model(slug: &str, visibility: ModelVisibility, priority: i32) ->
|
||||
slug,
|
||||
visibility,
|
||||
priority,
|
||||
TruncationPolicyConfig::bytes(10_000),
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,13 @@ async fn emits_warning_when_resumed_model_differs() {
|
||||
thread: conversation,
|
||||
..
|
||||
} = thread_manager
|
||||
.resume_thread_with_history(config, initial_history, auth_manager, false, None)
|
||||
.resume_thread_with_history(
|
||||
config,
|
||||
initial_history,
|
||||
auth_manager,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("resume conversation");
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ async fn review_op_emits_lifecycle_and_review_output() {
|
||||
]"#;
|
||||
let review_json_escaped = serde_json::to_string(&review_json).unwrap();
|
||||
let sse_raw = sse_template.replace("__REVIEW__", &review_json_escaped);
|
||||
let (server, _request_log) = start_responses_server_with_sse(&sse_raw, 1).await;
|
||||
let (server, _request_log) =
|
||||
start_responses_server_with_sse(&sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |_| {}).await;
|
||||
|
||||
@@ -192,7 +193,8 @@ async fn review_op_with_plain_text_emits_review_fallback() {
|
||||
}},
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, _request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, _request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |_| {}).await;
|
||||
|
||||
@@ -253,7 +255,8 @@ async fn review_filters_agent_message_related_events() {
|
||||
}},
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, _request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, _request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |_| {}).await;
|
||||
|
||||
@@ -335,7 +338,8 @@ async fn review_does_not_emit_agent_message_on_structured_output() {
|
||||
]"#;
|
||||
let review_json_escaped = serde_json::to_string(&review_json).unwrap();
|
||||
let sse_raw = sse_template.replace("__REVIEW__", &review_json_escaped);
|
||||
let (server, _request_log) = start_responses_server_with_sse(&sse_raw, 1).await;
|
||||
let (server, _request_log) =
|
||||
start_responses_server_with_sse(&sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |_| {}).await;
|
||||
|
||||
@@ -390,7 +394,8 @@ async fn review_uses_custom_review_model_from_config() {
|
||||
let sse_raw = r#"[
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
// Choose a review model different from the main model; ensure it is used.
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |cfg| {
|
||||
@@ -444,7 +449,8 @@ async fn review_uses_session_model_when_review_model_unset() {
|
||||
let sse_raw = r#"[
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |cfg| {
|
||||
cfg.model = Some("gpt-4.1".to_string());
|
||||
@@ -498,7 +504,8 @@ async fn review_input_isolated_from_parent_history() {
|
||||
let sse_raw = r#"[
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
|
||||
// Seed a parent session history via resume file with both user + assistant items.
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
@@ -681,7 +688,8 @@ async fn review_history_surfaces_in_parent_session() {
|
||||
}},
|
||||
{"type":"response.completed", "response": {"id": "__ID__"}}
|
||||
]"#;
|
||||
let (server, request_log) = start_responses_server_with_sse(sse_raw, 2).await;
|
||||
let (server, request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 2).await;
|
||||
let codex_home = Arc::new(TempDir::new().unwrap());
|
||||
let codex = new_conversation_for_server(&server, codex_home.clone(), |_| {}).await;
|
||||
|
||||
@@ -773,7 +781,8 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() {
|
||||
skip_if_no_network!();
|
||||
|
||||
let sse_raw = r#"[{"type":"response.completed", "response": {"id": "__ID__"}}]"#;
|
||||
let (server, request_log) = start_responses_server_with_sse(sse_raw, 1).await;
|
||||
let (server, request_log) =
|
||||
start_responses_server_with_sse(sse_raw, /*expected_requests*/ 1).await;
|
||||
|
||||
let initial_cwd = TempDir::new().unwrap();
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
context_window: Some(272_000),
|
||||
|
||||
@@ -60,7 +60,10 @@ async fn upsert_thread_metadata(codex_home: &Path, thread_id: ThreadId, rollout_
|
||||
let runtime = StateRuntime::init(codex_home.to_path_buf(), "test-provider".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
runtime.mark_backfill_complete(None).await.unwrap();
|
||||
runtime
|
||||
.mark_backfill_complete(/*last_watermark*/ None)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut builder = ThreadMetadataBuilder::new(
|
||||
thread_id,
|
||||
rollout_path,
|
||||
@@ -168,14 +171,14 @@ async fn find_locates_rollout_file_written_by_recorder() -> std::io::Result<()>
|
||||
&config,
|
||||
RolloutRecorderParams::new(
|
||||
thread_id,
|
||||
None,
|
||||
/*forked_from_id*/ None,
|
||||
SessionSource::Exec,
|
||||
BaseInstructions::default(),
|
||||
Vec::new(),
|
||||
EventPersistenceMode::Limited,
|
||||
),
|
||||
None,
|
||||
None,
|
||||
/*state_db_ctx*/ None,
|
||||
/*state_builder*/ None,
|
||||
)
|
||||
.await?;
|
||||
recorder.persist().await?;
|
||||
|
||||
@@ -192,7 +192,7 @@ assert os.read(master, 4) == b"ping""#
|
||||
&policy,
|
||||
sandbox_cwd.as_path(),
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
None,
|
||||
/*network*/ None,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await
|
||||
@@ -245,7 +245,7 @@ async fn java_home_finds_runtime_under_seatbelt() {
|
||||
&policy,
|
||||
sandbox_cwd.as_path(),
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
None,
|
||||
/*network*/ None,
|
||||
env,
|
||||
)
|
||||
.await
|
||||
@@ -302,7 +302,7 @@ async fn touch(path: &Path, policy: &SandboxPolicy) -> bool {
|
||||
policy,
|
||||
sandbox_cwd.as_path(),
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
None,
|
||||
/*network*/ None,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user