codex-python 0.2.15__tar.gz → 0.2.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-python
3
- Version: 0.2.15
3
+ Version: 0.2.16
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -128,3 +128,14 @@ Links
128
128
  - uv: https://docs.astral.sh/uv/
129
129
  - maturin: https://www.maturin.rs/
130
130
 
131
+ ## GitHub Workflow: Autonomous Review
132
+
133
+ This repo includes a workflow that runs autonomous code review on pull requests and can
134
+ apply edits on demand via a slash command.
135
+
136
+ - Automatic review runs on PR open, synchronize, reopen, and when marked ready for review.
137
+ - To request autonomous edits on a PR, comment `/codex` in either a PR thread or a
138
+ review comment. The workflow listens for `/codex` and triggers the "act" job.
139
+
140
+ See `.github/workflows/codex-autoreview.yml` for configuration.
141
+
@@ -108,3 +108,14 @@ Links
108
108
  - Codex repo: https://github.com/openai/codex
109
109
  - uv: https://docs.astral.sh/uv/
110
110
  - maturin: https://www.maturin.rs/
111
+
112
+ ## GitHub Workflow: Autonomous Review
113
+
114
+ This repo includes a workflow that runs autonomous code review on pull requests and can
115
+ apply edits on demand via a slash command.
116
+
117
+ - Automatic review runs on PR open, synchronize, reopen, and when marked ready for review.
118
+ - To request autonomous edits on a PR, comment `/codex` in either a PR thread or a
119
+ review comment. The workflow listens for `/codex` and triggers the "act" job.
120
+
121
+ See `.github/workflows/codex-autoreview.yml` for configuration.
@@ -31,4 +31,4 @@ __all__ = [
31
31
  ]
32
32
 
33
33
  # Package version. Kept in sync with Cargo.toml via CI before builds.
34
- __version__ = "0.2.15"
34
+ __version__ = "0.2.16"
@@ -16,10 +16,13 @@ class CodexError(Exception):
16
16
  class CodexNativeError(CodexError):
17
17
  """Raised when the native extension is not available or fails."""
18
18
 
19
- def __init__(self) -> None:
19
+ def __init__(self, message: str | None = None) -> None:
20
20
  super().__init__(
21
- "codex_native extension not installed or failed to run. "
22
- "Run `make dev-native` or ensure native wheels are installed."
21
+ message
22
+ or (
23
+ "codex_native extension not installed or failed to run. "
24
+ "Run `make dev-native` or ensure native wheels are installed."
25
+ )
23
26
  )
24
27
 
25
28
 
@@ -31,7 +34,14 @@ class Conversation:
31
34
 
32
35
  def __iter__(self) -> Iterator[Event]:
33
36
  """Yield `Event` objects from the native stream."""
34
- for item in self._stream:
37
+ iterator = iter(self._stream)
38
+ while True:
39
+ try:
40
+ item = next(iterator)
41
+ except StopIteration:
42
+ return
43
+ except RuntimeError as exc: # surfaced from native iterator
44
+ raise CodexNativeError(str(exc)) from exc
35
45
  try:
36
46
  yield Event.model_validate(item)
37
47
  except Exception:
@@ -76,7 +86,7 @@ class CodexClient:
76
86
  )
77
87
  return Conversation(_stream=stream)
78
88
  except RuntimeError as e:
79
- raise CodexNativeError() from e
89
+ raise CodexNativeError(str(e)) from e
80
90
 
81
91
 
82
92
  def run_exec(
@@ -97,7 +107,7 @@ def run_exec(
97
107
  load_default_config=load_default_config,
98
108
  )
99
109
  except RuntimeError as e:
100
- raise CodexNativeError() from e
110
+ raise CodexNativeError(str(e)) from e
101
111
 
102
112
  out: list[Event] = []
103
113
  for item in events:
@@ -218,7 +218,8 @@ class CodexConfig(BaseModel):
218
218
  project_doc_max_bytes: int | None = None
219
219
  profile: str | None = None
220
220
  profiles: dict[str, ProfileConfig] | None = None
221
- tools: ToolsConfig | None = None
221
+ # Accept either a structured ToolsConfig or a plain mapping (common in tests/CLI overrides)
222
+ tools: ToolsConfig | dict[str, Any] | None = None
222
223
  projects: dict[str, ProjectConfig] | None = None
223
224
 
224
225
  # Experimental / internal
@@ -311,7 +311,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
311
311
  [[package]]
312
312
  name = "codex-apply-patch"
313
313
  version = "0.0.0"
314
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
314
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
315
315
  dependencies = [
316
316
  "anyhow",
317
317
  "once_cell",
@@ -324,7 +324,7 @@ dependencies = [
324
324
  [[package]]
325
325
  name = "codex-core"
326
326
  version = "0.0.0"
327
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
327
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
328
328
  dependencies = [
329
329
  "anyhow",
330
330
  "askama",
@@ -374,7 +374,7 @@ dependencies = [
374
374
  [[package]]
375
375
  name = "codex-file-search"
376
376
  version = "0.0.0"
377
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
377
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
378
378
  dependencies = [
379
379
  "anyhow",
380
380
  "clap",
@@ -388,7 +388,7 @@ dependencies = [
388
388
  [[package]]
389
389
  name = "codex-mcp-client"
390
390
  version = "0.0.0"
391
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
391
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
392
392
  dependencies = [
393
393
  "anyhow",
394
394
  "mcp-types",
@@ -402,7 +402,7 @@ dependencies = [
402
402
  [[package]]
403
403
  name = "codex-protocol"
404
404
  version = "0.0.0"
405
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
405
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
406
406
  dependencies = [
407
407
  "base64",
408
408
  "icu_decimal",
@@ -422,7 +422,7 @@ dependencies = [
422
422
 
423
423
  [[package]]
424
424
  name = "codex_native"
425
- version = "0.2.15"
425
+ version = "0.2.16"
426
426
  dependencies = [
427
427
  "anyhow",
428
428
  "clap",
@@ -892,7 +892,7 @@ dependencies = [
892
892
  "cfg-if",
893
893
  "libc",
894
894
  "r-efi",
895
- "wasi 0.14.5+wasi-0.2.4",
895
+ "wasi 0.14.7+wasi-0.2.4",
896
896
  ]
897
897
 
898
898
  [[package]]
@@ -926,7 +926,7 @@ dependencies = [
926
926
  "futures-core",
927
927
  "futures-sink",
928
928
  "http",
929
- "indexmap 2.11.1",
929
+ "indexmap 2.11.3",
930
930
  "slab",
931
931
  "tokio",
932
932
  "tokio-util",
@@ -1071,9 +1071,9 @@ dependencies = [
1071
1071
 
1072
1072
  [[package]]
1073
1073
  name = "hyper-util"
1074
- version = "0.1.16"
1074
+ version = "0.1.17"
1075
1075
  source = "registry+https://github.com/rust-lang/crates.io-index"
1076
- checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1076
+ checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
1077
1077
  dependencies = [
1078
1078
  "base64",
1079
1079
  "bytes",
@@ -1306,13 +1306,14 @@ dependencies = [
1306
1306
 
1307
1307
  [[package]]
1308
1308
  name = "indexmap"
1309
- version = "2.11.1"
1309
+ version = "2.11.3"
1310
1310
  source = "registry+https://github.com/rust-lang/crates.io-index"
1311
- checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
1311
+ checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
1312
1312
  dependencies = [
1313
1313
  "equivalent",
1314
1314
  "hashbrown 0.15.5",
1315
1315
  "serde",
1316
+ "serde_core",
1316
1317
  ]
1317
1318
 
1318
1319
  [[package]]
@@ -1455,7 +1456,7 @@ dependencies = [
1455
1456
  [[package]]
1456
1457
  name = "mcp-types"
1457
1458
  version = "0.0.0"
1458
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
1459
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
1459
1460
  dependencies = [
1460
1461
  "serde",
1461
1462
  "serde_json",
@@ -1755,12 +1756,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1755
1756
 
1756
1757
  [[package]]
1757
1758
  name = "plist"
1758
- version = "1.7.4"
1759
+ version = "1.8.0"
1759
1760
  source = "registry+https://github.com/rust-lang/crates.io-index"
1760
- checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
1761
+ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
1761
1762
  dependencies = [
1762
1763
  "base64",
1763
- "indexmap 2.11.1",
1764
+ "indexmap 2.11.3",
1764
1765
  "quick-xml",
1765
1766
  "serde",
1766
1767
  "time",
@@ -2129,9 +2130,9 @@ dependencies = [
2129
2130
 
2130
2131
  [[package]]
2131
2132
  name = "rustls-webpki"
2132
- version = "0.103.5"
2133
+ version = "0.103.6"
2133
2134
  source = "registry+https://github.com/rust-lang/crates.io-index"
2134
- checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8"
2135
+ checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
2135
2136
  dependencies = [
2136
2137
  "ring",
2137
2138
  "rustls-pki-types",
@@ -2232,9 +2233,9 @@ dependencies = [
2232
2233
 
2233
2234
  [[package]]
2234
2235
  name = "serde"
2235
- version = "1.0.223"
2236
+ version = "1.0.225"
2236
2237
  source = "registry+https://github.com/rust-lang/crates.io-index"
2237
- checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
2238
+ checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
2238
2239
  dependencies = [
2239
2240
  "serde_core",
2240
2241
  "serde_derive",
@@ -2242,18 +2243,18 @@ dependencies = [
2242
2243
 
2243
2244
  [[package]]
2244
2245
  name = "serde_core"
2245
- version = "1.0.223"
2246
+ version = "1.0.225"
2246
2247
  source = "registry+https://github.com/rust-lang/crates.io-index"
2247
- checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
2248
+ checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
2248
2249
  dependencies = [
2249
2250
  "serde_derive",
2250
2251
  ]
2251
2252
 
2252
2253
  [[package]]
2253
2254
  name = "serde_derive"
2254
- version = "1.0.223"
2255
+ version = "1.0.225"
2255
2256
  source = "registry+https://github.com/rust-lang/crates.io-index"
2256
- checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
2257
+ checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
2257
2258
  dependencies = [
2258
2259
  "proc-macro2",
2259
2260
  "quote",
@@ -2266,7 +2267,7 @@ version = "1.0.145"
2266
2267
  source = "registry+https://github.com/rust-lang/crates.io-index"
2267
2268
  checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
2268
2269
  dependencies = [
2269
- "indexmap 2.11.1",
2270
+ "indexmap 2.11.3",
2270
2271
  "itoa",
2271
2272
  "memchr",
2272
2273
  "ryu",
@@ -2276,11 +2277,11 @@ dependencies = [
2276
2277
 
2277
2278
  [[package]]
2278
2279
  name = "serde_spanned"
2279
- version = "1.0.0"
2280
+ version = "1.0.1"
2280
2281
  source = "registry+https://github.com/rust-lang/crates.io-index"
2281
- checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
2282
+ checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6"
2282
2283
  dependencies = [
2283
- "serde",
2284
+ "serde_core",
2284
2285
  ]
2285
2286
 
2286
2287
  [[package]]
@@ -2305,7 +2306,7 @@ dependencies = [
2305
2306
  "chrono",
2306
2307
  "hex",
2307
2308
  "indexmap 1.9.3",
2308
- "indexmap 2.11.1",
2309
+ "indexmap 2.11.3",
2309
2310
  "schemars 0.9.0",
2310
2311
  "schemars 1.0.4",
2311
2312
  "serde",
@@ -2705,12 +2706,12 @@ dependencies = [
2705
2706
 
2706
2707
  [[package]]
2707
2708
  name = "toml"
2708
- version = "0.9.5"
2709
+ version = "0.9.6"
2709
2710
  source = "registry+https://github.com/rust-lang/crates.io-index"
2710
- checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
2711
+ checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201"
2711
2712
  dependencies = [
2712
- "indexmap 2.11.1",
2713
- "serde",
2713
+ "indexmap 2.11.3",
2714
+ "serde_core",
2714
2715
  "serde_spanned",
2715
2716
  "toml_datetime",
2716
2717
  "toml_parser",
@@ -2720,20 +2721,20 @@ dependencies = [
2720
2721
 
2721
2722
  [[package]]
2722
2723
  name = "toml_datetime"
2723
- version = "0.7.0"
2724
+ version = "0.7.1"
2724
2725
  source = "registry+https://github.com/rust-lang/crates.io-index"
2725
- checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
2726
+ checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be"
2726
2727
  dependencies = [
2727
- "serde",
2728
+ "serde_core",
2728
2729
  ]
2729
2730
 
2730
2731
  [[package]]
2731
2732
  name = "toml_edit"
2732
- version = "0.23.4"
2733
+ version = "0.23.5"
2733
2734
  source = "registry+https://github.com/rust-lang/crates.io-index"
2734
- checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93"
2735
+ checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9"
2735
2736
  dependencies = [
2736
- "indexmap 2.11.1",
2737
+ "indexmap 2.11.3",
2737
2738
  "toml_datetime",
2738
2739
  "toml_parser",
2739
2740
  "toml_writer",
@@ -3039,18 +3040,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3039
3040
 
3040
3041
  [[package]]
3041
3042
  name = "wasi"
3042
- version = "0.14.5+wasi-0.2.4"
3043
+ version = "0.14.7+wasi-0.2.4"
3043
3044
  source = "registry+https://github.com/rust-lang/crates.io-index"
3044
- checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
3045
+ checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
3045
3046
  dependencies = [
3046
3047
  "wasip2",
3047
3048
  ]
3048
3049
 
3049
3050
  [[package]]
3050
3051
  name = "wasip2"
3051
- version = "1.0.0+wasi-0.2.4"
3052
+ version = "1.0.1+wasi-0.2.4"
3052
3053
  source = "registry+https://github.com/rust-lang/crates.io-index"
3053
- checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
3054
+ checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
3054
3055
  dependencies = [
3055
3056
  "wit-bindgen",
3056
3057
  ]
@@ -3484,9 +3485,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
3484
3485
 
3485
3486
  [[package]]
3486
3487
  name = "wit-bindgen"
3487
- version = "0.45.1"
3488
+ version = "0.46.0"
3488
3489
  source = "registry+https://github.com/rust-lang/crates.io-index"
3489
- checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
3490
+ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
3490
3491
 
3491
3492
  [[package]]
3492
3493
  name = "writeable"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex_native"
3
- version = "0.2.15"
3
+ version = "0.2.16"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -15,7 +15,11 @@ struct Args {
15
15
  ts_out: PathBuf,
16
16
 
17
17
  /// Output directory for the generated JSON Schema (.generated/schema by default)
18
- #[arg(long = "schema-out", value_name = "DIR", default_value = ".generated/schema")]
18
+ #[arg(
19
+ long = "schema-out",
20
+ value_name = "DIR",
21
+ default_value = ".generated/schema"
22
+ )]
19
23
  schema_out: PathBuf,
20
24
 
21
25
  /// Optional path to the Prettier binary to format emitted TS (forwarded to upstream generator)
@@ -63,9 +67,12 @@ fn ensure_ts_generated(ts_out: &Path, prettier: Option<&Path>) -> Result<()> {
63
67
  .arg(relative_path_from(&monorepo, ts_out)?)
64
68
  .current_dir(&monorepo);
65
69
  if let Some(bin) = prettier {
66
- cmd.arg("--prettier").arg(relative_path_from(&monorepo, bin)?);
70
+ cmd.arg("--prettier")
71
+ .arg(relative_path_from(&monorepo, bin)?);
67
72
  }
68
- let status = cmd.status().context("Failed to run codex-protocol-ts generator")?;
73
+ let status = cmd
74
+ .status()
75
+ .context("Failed to run codex-protocol-ts generator")?;
69
76
  if status.success() {
70
77
  return Ok(());
71
78
  }
@@ -6,11 +6,11 @@ use codex_core::{AuthManager, CodexAuth, ConversationManager};
6
6
  use pyo3::prelude::*;
7
7
  use pyo3::types::{PyDict, PyFloat, PyList, PyModule, PyString};
8
8
  use serde_json::Value as JsonValue;
9
- use toml::value::Value as TomlValue;
10
- use toml::value::Table as TomlTable;
11
9
  use std::path::PathBuf;
12
10
  use std::sync::{mpsc, Arc, Mutex};
13
11
  use std::thread;
12
+ use toml::value::Table as TomlTable;
13
+ use toml::value::Value as TomlValue;
14
14
 
15
15
  #[pyfunction]
16
16
  fn run_exec_collect(
@@ -42,9 +42,7 @@ async fn run_exec_impl(prompt: String, config: Config) -> Result<Vec<JsonValue>>
42
42
  Ok(val) if !val.trim().is_empty() => {
43
43
  ConversationManager::with_auth(CodexAuth::from_api_key(&val))
44
44
  }
45
- _ => ConversationManager::new(AuthManager::shared(
46
- config.codex_home.clone(),
47
- )),
45
+ _ => ConversationManager::new(AuthManager::shared(config.codex_home.clone())),
48
46
  };
49
47
  let new_conv = conversation_manager.new_conversation(config).await?;
50
48
  let conversation = new_conv.conversation.clone();
@@ -66,13 +64,15 @@ async fn run_exec_impl(prompt: String, config: Config) -> Result<Vec<JsonValue>>
66
64
  out.push(serde_json::to_value(&ev)?);
67
65
  if is_complete {
68
66
  // Ask the agent to shutdown; collect remaining events
69
- let _ = conversation.submit(codex_core::protocol::Op::Shutdown).await;
67
+ let _ = conversation
68
+ .submit(codex_core::protocol::Op::Shutdown)
69
+ .await;
70
70
  }
71
71
  if is_shutdown {
72
72
  break;
73
73
  }
74
74
  }
75
- Err(_) => break,
75
+ Err(err) => return Err(err.into()),
76
76
  }
77
77
  }
78
78
  Ok(out)
@@ -84,7 +84,7 @@ fn to_py<E: std::fmt::Display>(e: E) -> PyErr {
84
84
 
85
85
  #[pyclass]
86
86
  struct CodexEventStream {
87
- rx: Arc<Mutex<mpsc::Receiver<JsonValue>>>,
87
+ rx: Arc<Mutex<mpsc::Receiver<Result<JsonValue, String>>>>,
88
88
  }
89
89
 
90
90
  #[pymethods]
@@ -93,12 +93,13 @@ impl CodexEventStream {
93
93
  slf
94
94
  }
95
95
 
96
- fn __next__(&mut self, py: Python<'_>) -> Option<Py<PyAny>> {
96
+ fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
97
97
  // Run the blocking recv without holding the GIL
98
- let res = py.detach(|| self.rx.lock().ok()?.recv().ok());
98
+ let res = py.detach(|| self.rx.lock().ok().and_then(|rx| rx.recv().ok()));
99
99
  match res {
100
- Some(v) => json_to_py(py, &v).ok(),
101
- None => None,
100
+ Some(Ok(v)) => Ok(Some(json_to_py(py, &v)?)),
101
+ Some(Err(msg)) => Err(pyo3::exceptions::PyRuntimeError::new_err(msg)),
102
+ None => Ok(None),
102
103
  }
103
104
  }
104
105
  }
@@ -109,15 +110,18 @@ fn start_exec_stream(
109
110
  config_overrides: Option<Bound<'_, PyDict>>,
110
111
  load_default_config: bool,
111
112
  ) -> PyResult<CodexEventStream> {
112
- let (tx, rx) = mpsc::channel::<JsonValue>();
113
+ let (tx, rx) = mpsc::channel::<Result<JsonValue, String>>();
113
114
 
114
115
  // Build a pure-Rust Config on the Python thread
115
116
  let config = build_config(config_overrides, load_default_config).map_err(to_py)?;
116
117
  let prompt_clone = prompt.clone();
117
118
 
118
119
  thread::spawn(move || {
119
- if let Err(e) = run_exec_stream_impl(prompt_clone, config, tx) {
120
- eprintln!("codex_native stream error: {e}");
120
+ let tx_for_impl = tx.clone();
121
+ if let Err(e) = run_exec_stream_impl(prompt_clone, config, tx_for_impl) {
122
+ let msg = e.to_string();
123
+ let _ = tx.send(Err(msg.clone()));
124
+ eprintln!("codex_native stream error: {msg}");
121
125
  }
122
126
  });
123
127
  Ok(CodexEventStream {
@@ -174,10 +178,7 @@ fn json_to_py(py: Python<'_>, v: &JsonValue) -> PyResult<Py<PyAny>> {
174
178
  Ok(obj)
175
179
  }
176
180
 
177
- fn build_config(
178
- overrides: Option<Bound<'_, PyDict>>,
179
- load_default_config: bool,
180
- ) -> Result<Config> {
181
+ fn build_config(overrides: Option<Bound<'_, PyDict>>, load_default_config: bool) -> Result<Config> {
181
182
  // Match CLI behavior: import env vars from ~/.codex/.env (if present)
182
183
  // before reading config/auth so OPENAI_API_KEY and friends are visible.
183
184
  // Security: filter out CODEX_* variables just like the CLI does.
@@ -281,7 +282,10 @@ fn build_config(
281
282
 
282
283
  if load_default_config {
283
284
  // Start from built-in defaults and apply CLI + typed overrides.
284
- Ok(Config::load_with_cli_overrides(cli_overrides, overrides_struct)?)
285
+ Ok(Config::load_with_cli_overrides(
286
+ cli_overrides,
287
+ overrides_struct,
288
+ )?)
285
289
  } else {
286
290
  // Do NOT read any on-disk config. Build a TOML value purely from CLI-style overrides
287
291
  // and then apply the strongly-typed overrides on top. We still resolve CODEX_HOME to
@@ -296,7 +300,11 @@ fn build_config(
296
300
 
297
301
  let root_value = TomlValue::Table(base_tbl);
298
302
  let cfg: ConfigToml = root_value.try_into().map_err(|e| anyhow::anyhow!(e))?;
299
- Ok(Config::load_from_base_config_with_overrides(cfg, overrides_struct, codex_home)?)
303
+ Ok(Config::load_from_base_config_with_overrides(
304
+ cfg,
305
+ overrides_struct,
306
+ codex_home,
307
+ )?)
300
308
  }
301
309
  }
302
310
 
@@ -430,16 +438,18 @@ fn insert_parts(current: &mut TomlTable, parts: &[&str], val: TomlValue) {
430
438
  }
431
439
  }
432
440
 
433
- fn run_exec_stream_impl(prompt: String, config: Config, tx: mpsc::Sender<JsonValue>) -> Result<()> {
441
+ fn run_exec_stream_impl(
442
+ prompt: String,
443
+ config: Config,
444
+ tx: mpsc::Sender<Result<JsonValue, String>>,
445
+ ) -> Result<()> {
434
446
  let rt = tokio::runtime::Runtime::new()?;
435
447
  rt.block_on(async move {
436
448
  let conversation_manager = match std::env::var("OPENAI_API_KEY") {
437
449
  Ok(val) if !val.trim().is_empty() => {
438
450
  ConversationManager::with_auth(CodexAuth::from_api_key(&val))
439
451
  }
440
- _ => ConversationManager::new(AuthManager::shared(
441
- config.codex_home.clone(),
442
- )),
452
+ _ => ConversationManager::new(AuthManager::shared(config.codex_home.clone())),
443
453
  };
444
454
  let new_conv = conversation_manager.new_conversation(config).await?;
445
455
  let conversation = new_conv.conversation.clone();
@@ -456,15 +466,20 @@ fn run_exec_stream_impl(prompt: String, config: Config, tx: mpsc::Sender<JsonVal
456
466
  Ok(ev) => {
457
467
  let is_shutdown = matches!(ev.msg, EventMsg::ShutdownComplete);
458
468
  let is_complete = matches!(ev.msg, EventMsg::TaskComplete(_));
459
- let _ = tx.send(serde_json::to_value(&ev)?);
469
+ let event_json = serde_json::to_value(&ev)?;
470
+ if tx.send(Ok(event_json)).is_err() {
471
+ break;
472
+ }
460
473
  if is_complete {
461
- let _ = conversation.submit(codex_core::protocol::Op::Shutdown).await;
474
+ let _ = conversation
475
+ .submit(codex_core::protocol::Op::Shutdown)
476
+ .await;
462
477
  }
463
478
  if is_shutdown {
464
479
  break;
465
480
  }
466
481
  }
467
- Err(_) => break,
482
+ Err(err) => return Err(err.into()),
468
483
  }
469
484
  }
470
485
  Ok::<(), anyhow::Error>(())
@@ -64,3 +64,9 @@ disallow_untyped_defs = true
64
64
  no_implicit_optional = true
65
65
  check_untyped_defs = true
66
66
  ignore_missing_imports = true
67
+ exclude = [
68
+ "^codex-proj/",
69
+ "^\\.generated/",
70
+ "^crates/.*/target/",
71
+ "^dist/",
72
+ ]
File without changes