Skip to content

Commit ab6411f

Browse files
authored
Merge branch 'main' into add-github-action-for-nix
2 parents bfc3deb + b016a3e commit ab6411f

File tree

59 files changed

+1679
-715
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1679
-715
lines changed

AGENTS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,28 @@ If you don’t have the tool:
7373
### Test assertions
7474

7575
- Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already.
76+
77+
### Integration tests (core)
78+
79+
- Prefer the utilities in `core_test_support::responses` when writing end-to-end Codex tests.
80+
81+
- All `mount_sse*` helpers return a `ResponseMock`; hold onto it so you can assert against outbound `/responses` POST bodies.
82+
- Use `ResponseMock::single_request()` when a test should only issue one POST, or `ResponseMock::requests()` to inspect every captured `ResponsesRequest`.
83+
- `ResponsesRequest` exposes helpers (`body_json`, `input`, `function_call_output`, `custom_tool_call_output`, `call_output`, `header`, `path`, `query_param`) so assertions can target structured payloads instead of manual JSON digging.
84+
- Build SSE payloads with the provided `ev_*` constructors and the `sse(...)`.
85+
86+
- Typical pattern:
87+
88+
```rust
89+
let mock = responses::mount_sse_once(&server, responses::sse(vec![
90+
responses::ev_response_created("resp-1"),
91+
responses::ev_function_call(call_id, "shell", &serde_json::to_string(&args)?),
92+
responses::ev_completed("resp-1"),
93+
])).await;
94+
95+
codex.submit(Op::UserTurn { ... }).await?;
96+
97+
// Assert request body if needed.
98+
let request = mock.single_request();
99+
// assert using request.function_call_output(call_id) or request.json_body() or other helpers.
100+
```

codex-rs/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/cli/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
294294
last,
295295
config_overrides,
296296
);
297-
codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
297+
let exit_info = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
298+
print_exit_messages(exit_info);
298299
}
299300
Some(Subcommand::Login(mut login_cli)) => {
300301
prepend_config_flags(

codex-rs/cloud-tasks/Cargo.toml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ path = "src/lib.rs"
1111
workspace = true
1212

1313
[dependencies]
14-
anyhow = "1"
15-
base64 = "0.22"
16-
chrono = { version = "0.4", features = ["serde"] }
17-
clap = { version = "4", features = ["derive"] }
14+
anyhow = { workspace = true }
15+
base64 = { workspace = true }
16+
chrono = { workspace = true, features = ["serde"] }
17+
clap = { workspace = true, features = ["derive"] }
1818
codex-cloud-tasks-client = { path = "../cloud-tasks-client", features = [
1919
"mock",
2020
"online",
@@ -23,16 +23,16 @@ codex-common = { path = "../common", features = ["cli"] }
2323
codex-core = { path = "../core" }
2424
codex-login = { path = "../login" }
2525
codex-tui = { path = "../tui" }
26-
crossterm = { version = "0.28.1", features = ["event-stream"] }
27-
ratatui = { version = "0.29.0" }
28-
reqwest = { version = "0.12", features = ["json"] }
29-
serde = { version = "1", features = ["derive"] }
30-
serde_json = "1"
31-
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
32-
tokio-stream = "0.1.17"
33-
tracing = { version = "0.1.41", features = ["log"] }
34-
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
35-
unicode-width = "0.1"
26+
crossterm = { workspace = true, features = ["event-stream"] }
27+
ratatui = { workspace = true }
28+
reqwest = { workspace = true, features = ["json"] }
29+
serde = { workspace = true, features = ["derive"] }
30+
serde_json = { workspace = true }
31+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
32+
tokio-stream = { workspace = true }
33+
tracing = { workspace = true, features = ["log"] }
34+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
35+
unicode-width = { workspace = true }
3636

3737
[dev-dependencies]
38-
async-trait = "0.1"
38+
async-trait = { workspace = true }

codex-rs/core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ tokio = { workspace = true, features = [
6161
"rt-multi-thread",
6262
"signal",
6363
] }
64-
tokio-util = { workspace = true }
64+
tokio-util = { workspace = true, features = ["rt"] }
6565
toml = { workspace = true }
6666
toml_edit = { workspace = true }
6767
tracing = { workspace = true, features = ["log"] }

codex-rs/core/src/codex.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ use codex_protocol::protocol::SessionSource;
2323
use codex_protocol::protocol::TaskStartedEvent;
2424
use codex_protocol::protocol::TurnAbortReason;
2525
use codex_protocol::protocol::TurnContextItem;
26+
use futures::future::BoxFuture;
2627
use futures::prelude::*;
28+
use futures::stream::FuturesOrdered;
2729
use mcp_types::CallToolResult;
2830
use serde_json;
2931
use serde_json::Value;
@@ -1584,7 +1586,7 @@ async fn spawn_review_thread(
15841586

15851587
// Seed the child task with the review prompt as the initial user message.
15861588
let input: Vec<InputItem> = vec![InputItem::Text {
1587-
text: format!("{base_instructions}\n\n---\n\nNow, here's your task: {review_prompt}"),
1589+
text: review_prompt,
15881590
}];
15891591
let tc = Arc::new(review_turn_context);
15901592

@@ -2101,39 +2103,33 @@ async fn try_run_turn(
21012103
sess.persist_rollout_items(&[rollout_item]).await;
21022104
let mut stream = turn_context.client.clone().stream(&prompt).await?;
21032105

2104-
let mut output = Vec::new();
2105-
let mut tool_runtime = ToolCallRuntime::new(
2106+
let tool_runtime = ToolCallRuntime::new(
21062107
Arc::clone(&router),
21072108
Arc::clone(&sess),
21082109
Arc::clone(&turn_context),
21092110
Arc::clone(&turn_diff_tracker),
21102111
sub_id.to_string(),
21112112
);
2113+
let mut output: FuturesOrdered<BoxFuture<CodexResult<ProcessedResponseItem>>> =
2114+
FuturesOrdered::new();
21122115

21132116
loop {
21142117
// Poll the next item from the model stream. We must inspect *both* Ok and Err
21152118
// cases so that transient stream failures (e.g., dropped SSE connection before
21162119
// `response.completed`) bubble up and trigger the caller's retry logic.
21172120
let event = stream.next().await;
21182121
let event = match event {
2119-
Some(event) => event,
2122+
Some(res) => res?,
21202123
None => {
2121-
tool_runtime.abort_all();
21222124
return Err(CodexErr::Stream(
21232125
"stream closed before response.completed".into(),
21242126
None,
21252127
));
21262128
}
21272129
};
21282130

2129-
let event = match event {
2130-
Ok(ev) => ev,
2131-
Err(e) => {
2132-
tool_runtime.abort_all();
2133-
// Propagate the underlying stream error to the caller (run_turn), which
2134-
// will apply the configured `stream_max_retries` policy.
2135-
return Err(e);
2136-
}
2131+
let add_completed = &mut |response_item: ProcessedResponseItem| {
2132+
output.push_back(future::ready(Ok(response_item)).boxed());
21372133
};
21382134

21392135
match event {
@@ -2143,14 +2139,18 @@ async fn try_run_turn(
21432139
Ok(Some(call)) => {
21442140
let payload_preview = call.payload.log_payload().into_owned();
21452141
tracing::info!("ToolCall: {} {}", call.tool_name, payload_preview);
2146-
let index = output.len();
2147-
output.push(ProcessedResponseItem {
2148-
item,
2149-
response: None,
2150-
});
2151-
tool_runtime
2152-
.handle_tool_call(call, index, output.as_mut_slice())
2153-
.await?;
2142+
2143+
let response = tool_runtime.handle_tool_call(call);
2144+
2145+
output.push_back(
2146+
async move {
2147+
Ok(ProcessedResponseItem {
2148+
item,
2149+
response: Some(response.await?),
2150+
})
2151+
}
2152+
.boxed(),
2153+
);
21542154
}
21552155
Ok(None) => {
21562156
let response = handle_non_tool_response_item(
@@ -2160,7 +2160,7 @@ async fn try_run_turn(
21602160
item.clone(),
21612161
)
21622162
.await?;
2163-
output.push(ProcessedResponseItem { item, response });
2163+
add_completed(ProcessedResponseItem { item, response });
21642164
}
21652165
Err(FunctionCallError::MissingLocalShellCallId) => {
21662166
let msg = "LocalShellCall without call_id or id";
@@ -2177,7 +2177,7 @@ async fn try_run_turn(
21772177
success: None,
21782178
},
21792179
};
2180-
output.push(ProcessedResponseItem {
2180+
add_completed(ProcessedResponseItem {
21812181
item,
21822182
response: Some(response),
21832183
});
@@ -2190,7 +2190,7 @@ async fn try_run_turn(
21902190
success: None,
21912191
},
21922192
};
2193-
output.push(ProcessedResponseItem {
2193+
add_completed(ProcessedResponseItem {
21942194
item,
21952195
response: Some(response),
21962196
});
@@ -2221,7 +2221,7 @@ async fn try_run_turn(
22212221
sess.update_token_usage_info(sub_id, turn_context.as_ref(), token_usage.as_ref())
22222222
.await;
22232223

2224-
tool_runtime.resolve_pending(output.as_mut_slice()).await?;
2224+
let processed_items: Vec<ProcessedResponseItem> = output.try_collect().await?;
22252225

22262226
let unified_diff = {
22272227
let mut tracker = turn_diff_tracker.lock().await;
@@ -2237,7 +2237,7 @@ async fn try_run_turn(
22372237
}
22382238

22392239
let result = TurnRunResult {
2240-
processed_items: output,
2240+
processed_items,
22412241
total_token_usage: token_usage.clone(),
22422242
};
22432243

codex-rs/core/src/model_family.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub fn find_family_for_model(mut slug: &str) -> Option<ModelFamily> {
120120
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
121121
experimental_supported_tools: vec![
122122
"read_file".to_string(),
123+
"list_dir".to_string(),
123124
"test_sync_tool".to_string()
124125
],
125126
supports_parallel_tool_calls: true,
@@ -133,7 +134,7 @@ pub fn find_family_for_model(mut slug: &str) -> Option<ModelFamily> {
133134
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
134135
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
135136
apply_patch_tool_type: Some(ApplyPatchToolType::Freeform),
136-
experimental_supported_tools: vec!["read_file".to_string()],
137+
experimental_supported_tools: vec!["read_file".to_string(), "list_dir".to_string()],
137138
supports_parallel_tool_calls: true,
138139
)
139140

0 commit comments

Comments
 (0)