codex-python 0.2.14__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.14
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.14"
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,12 +422,13 @@ dependencies = [
422
422
 
423
423
  [[package]]
424
424
  name = "codex_native"
425
- version = "0.2.14"
425
+ version = "0.2.16"
426
426
  dependencies = [
427
427
  "anyhow",
428
428
  "clap",
429
429
  "codex-core",
430
430
  "codex-protocol",
431
+ "dotenvy",
431
432
  "openssl-sys",
432
433
  "pathdiff",
433
434
  "pyo3",
@@ -601,6 +602,12 @@ dependencies = [
601
602
  "syn",
602
603
  ]
603
604
 
605
+ [[package]]
606
+ name = "dotenvy"
607
+ version = "0.15.7"
608
+ source = "registry+https://github.com/rust-lang/crates.io-index"
609
+ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
610
+
604
611
  [[package]]
605
612
  name = "downcast-rs"
606
613
  version = "1.2.1"
@@ -885,7 +892,7 @@ dependencies = [
885
892
  "cfg-if",
886
893
  "libc",
887
894
  "r-efi",
888
- "wasi 0.14.5+wasi-0.2.4",
895
+ "wasi 0.14.7+wasi-0.2.4",
889
896
  ]
890
897
 
891
898
  [[package]]
@@ -919,7 +926,7 @@ dependencies = [
919
926
  "futures-core",
920
927
  "futures-sink",
921
928
  "http",
922
- "indexmap 2.11.1",
929
+ "indexmap 2.11.3",
923
930
  "slab",
924
931
  "tokio",
925
932
  "tokio-util",
@@ -1064,9 +1071,9 @@ dependencies = [
1064
1071
 
1065
1072
  [[package]]
1066
1073
  name = "hyper-util"
1067
- version = "0.1.16"
1074
+ version = "0.1.17"
1068
1075
  source = "registry+https://github.com/rust-lang/crates.io-index"
1069
- checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1076
+ checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
1070
1077
  dependencies = [
1071
1078
  "base64",
1072
1079
  "bytes",
@@ -1299,13 +1306,14 @@ dependencies = [
1299
1306
 
1300
1307
  [[package]]
1301
1308
  name = "indexmap"
1302
- version = "2.11.1"
1309
+ version = "2.11.3"
1303
1310
  source = "registry+https://github.com/rust-lang/crates.io-index"
1304
- checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
1311
+ checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
1305
1312
  dependencies = [
1306
1313
  "equivalent",
1307
1314
  "hashbrown 0.15.5",
1308
1315
  "serde",
1316
+ "serde_core",
1309
1317
  ]
1310
1318
 
1311
1319
  [[package]]
@@ -1448,7 +1456,7 @@ dependencies = [
1448
1456
  [[package]]
1449
1457
  name = "mcp-types"
1450
1458
  version = "0.0.0"
1451
- source = "git+https://github.com/openai/codex?branch=main#fdf4a686463f13b56ee83f807bccbe3457542564"
1459
+ source = "git+https://github.com/openai/codex?branch=main#a8026d3846486e790827b26e9abf13e7f96e48bf"
1452
1460
  dependencies = [
1453
1461
  "serde",
1454
1462
  "serde_json",
@@ -1748,12 +1756,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1748
1756
 
1749
1757
  [[package]]
1750
1758
  name = "plist"
1751
- version = "1.7.4"
1759
+ version = "1.8.0"
1752
1760
  source = "registry+https://github.com/rust-lang/crates.io-index"
1753
- checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
1761
+ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
1754
1762
  dependencies = [
1755
1763
  "base64",
1756
- "indexmap 2.11.1",
1764
+ "indexmap 2.11.3",
1757
1765
  "quick-xml",
1758
1766
  "serde",
1759
1767
  "time",
@@ -2122,9 +2130,9 @@ dependencies = [
2122
2130
 
2123
2131
  [[package]]
2124
2132
  name = "rustls-webpki"
2125
- version = "0.103.5"
2133
+ version = "0.103.6"
2126
2134
  source = "registry+https://github.com/rust-lang/crates.io-index"
2127
- checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8"
2135
+ checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
2128
2136
  dependencies = [
2129
2137
  "ring",
2130
2138
  "rustls-pki-types",
@@ -2225,9 +2233,9 @@ dependencies = [
2225
2233
 
2226
2234
  [[package]]
2227
2235
  name = "serde"
2228
- version = "1.0.223"
2236
+ version = "1.0.225"
2229
2237
  source = "registry+https://github.com/rust-lang/crates.io-index"
2230
- checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
2238
+ checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
2231
2239
  dependencies = [
2232
2240
  "serde_core",
2233
2241
  "serde_derive",
@@ -2235,18 +2243,18 @@ dependencies = [
2235
2243
 
2236
2244
  [[package]]
2237
2245
  name = "serde_core"
2238
- version = "1.0.223"
2246
+ version = "1.0.225"
2239
2247
  source = "registry+https://github.com/rust-lang/crates.io-index"
2240
- checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
2248
+ checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
2241
2249
  dependencies = [
2242
2250
  "serde_derive",
2243
2251
  ]
2244
2252
 
2245
2253
  [[package]]
2246
2254
  name = "serde_derive"
2247
- version = "1.0.223"
2255
+ version = "1.0.225"
2248
2256
  source = "registry+https://github.com/rust-lang/crates.io-index"
2249
- checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
2257
+ checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
2250
2258
  dependencies = [
2251
2259
  "proc-macro2",
2252
2260
  "quote",
@@ -2259,7 +2267,7 @@ version = "1.0.145"
2259
2267
  source = "registry+https://github.com/rust-lang/crates.io-index"
2260
2268
  checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
2261
2269
  dependencies = [
2262
- "indexmap 2.11.1",
2270
+ "indexmap 2.11.3",
2263
2271
  "itoa",
2264
2272
  "memchr",
2265
2273
  "ryu",
@@ -2269,11 +2277,11 @@ dependencies = [
2269
2277
 
2270
2278
  [[package]]
2271
2279
  name = "serde_spanned"
2272
- version = "1.0.0"
2280
+ version = "1.0.1"
2273
2281
  source = "registry+https://github.com/rust-lang/crates.io-index"
2274
- checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
2282
+ checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6"
2275
2283
  dependencies = [
2276
- "serde",
2284
+ "serde_core",
2277
2285
  ]
2278
2286
 
2279
2287
  [[package]]
@@ -2298,7 +2306,7 @@ dependencies = [
2298
2306
  "chrono",
2299
2307
  "hex",
2300
2308
  "indexmap 1.9.3",
2301
- "indexmap 2.11.1",
2309
+ "indexmap 2.11.3",
2302
2310
  "schemars 0.9.0",
2303
2311
  "schemars 1.0.4",
2304
2312
  "serde",
@@ -2698,12 +2706,12 @@ dependencies = [
2698
2706
 
2699
2707
  [[package]]
2700
2708
  name = "toml"
2701
- version = "0.9.5"
2709
+ version = "0.9.6"
2702
2710
  source = "registry+https://github.com/rust-lang/crates.io-index"
2703
- checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
2711
+ checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201"
2704
2712
  dependencies = [
2705
- "indexmap 2.11.1",
2706
- "serde",
2713
+ "indexmap 2.11.3",
2714
+ "serde_core",
2707
2715
  "serde_spanned",
2708
2716
  "toml_datetime",
2709
2717
  "toml_parser",
@@ -2713,20 +2721,20 @@ dependencies = [
2713
2721
 
2714
2722
  [[package]]
2715
2723
  name = "toml_datetime"
2716
- version = "0.7.0"
2724
+ version = "0.7.1"
2717
2725
  source = "registry+https://github.com/rust-lang/crates.io-index"
2718
- checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
2726
+ checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be"
2719
2727
  dependencies = [
2720
- "serde",
2728
+ "serde_core",
2721
2729
  ]
2722
2730
 
2723
2731
  [[package]]
2724
2732
  name = "toml_edit"
2725
- version = "0.23.4"
2733
+ version = "0.23.5"
2726
2734
  source = "registry+https://github.com/rust-lang/crates.io-index"
2727
- checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93"
2735
+ checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9"
2728
2736
  dependencies = [
2729
- "indexmap 2.11.1",
2737
+ "indexmap 2.11.3",
2730
2738
  "toml_datetime",
2731
2739
  "toml_parser",
2732
2740
  "toml_writer",
@@ -3032,18 +3040,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3032
3040
 
3033
3041
  [[package]]
3034
3042
  name = "wasi"
3035
- version = "0.14.5+wasi-0.2.4"
3043
+ version = "0.14.7+wasi-0.2.4"
3036
3044
  source = "registry+https://github.com/rust-lang/crates.io-index"
3037
- checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
3045
+ checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
3038
3046
  dependencies = [
3039
3047
  "wasip2",
3040
3048
  ]
3041
3049
 
3042
3050
  [[package]]
3043
3051
  name = "wasip2"
3044
- version = "1.0.0+wasi-0.2.4"
3052
+ version = "1.0.1+wasi-0.2.4"
3045
3053
  source = "registry+https://github.com/rust-lang/crates.io-index"
3046
- checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
3054
+ checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
3047
3055
  dependencies = [
3048
3056
  "wit-bindgen",
3049
3057
  ]
@@ -3477,9 +3485,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
3477
3485
 
3478
3486
  [[package]]
3479
3487
  name = "wit-bindgen"
3480
- version = "0.45.1"
3488
+ version = "0.46.0"
3481
3489
  source = "registry+https://github.com/rust-lang/crates.io-index"
3482
- checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
3490
+ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
3483
3491
 
3484
3492
  [[package]]
3485
3493
  name = "writeable"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex_native"
3
- version = "0.2.14"
3
+ version = "0.2.16"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -22,6 +22,7 @@ tracing = "0.1"
22
22
  clap = { version = "4", features = ["derive"] }
23
23
  which = "6"
24
24
  pathdiff = "0.2"
25
+ dotenvy = "0.15.7"
25
26
 
26
27
  # Upstream Codex crates from the monorepo (use git deps; pin to main for now)
27
28
  # Pin to a specific commit of the upstream Codex monorepo to avoid breaking API changes
@@ -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
  }
@@ -1,16 +1,16 @@
1
1
  use anyhow::{Context, Result};
2
2
  use codex_core::config::{find_codex_home, Config, ConfigOverrides, ConfigToml};
3
3
  use codex_core::protocol::{EventMsg, InputItem};
4
- use codex_core::{AuthManager, ConversationManager};
4
+ use codex_core::{AuthManager, CodexAuth, ConversationManager};
5
5
  // use of SandboxMode is handled within core::config; not needed here
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(
@@ -38,9 +38,12 @@ fn run_exec_collect(
38
38
  }
39
39
 
40
40
  async fn run_exec_impl(prompt: String, config: Config) -> Result<Vec<JsonValue>> {
41
- let conversation_manager = ConversationManager::new(AuthManager::shared(
42
- config.codex_home.clone(),
43
- ));
41
+ let conversation_manager = match std::env::var("OPENAI_API_KEY") {
42
+ Ok(val) if !val.trim().is_empty() => {
43
+ ConversationManager::with_auth(CodexAuth::from_api_key(&val))
44
+ }
45
+ _ => ConversationManager::new(AuthManager::shared(config.codex_home.clone())),
46
+ };
44
47
  let new_conv = conversation_manager.new_conversation(config).await?;
45
48
  let conversation = new_conv.conversation.clone();
46
49
 
@@ -61,13 +64,15 @@ async fn run_exec_impl(prompt: String, config: Config) -> Result<Vec<JsonValue>>
61
64
  out.push(serde_json::to_value(&ev)?);
62
65
  if is_complete {
63
66
  // Ask the agent to shutdown; collect remaining events
64
- let _ = conversation.submit(codex_core::protocol::Op::Shutdown).await;
67
+ let _ = conversation
68
+ .submit(codex_core::protocol::Op::Shutdown)
69
+ .await;
65
70
  }
66
71
  if is_shutdown {
67
72
  break;
68
73
  }
69
74
  }
70
- Err(_) => break,
75
+ Err(err) => return Err(err.into()),
71
76
  }
72
77
  }
73
78
  Ok(out)
@@ -79,7 +84,7 @@ fn to_py<E: std::fmt::Display>(e: E) -> PyErr {
79
84
 
80
85
  #[pyclass]
81
86
  struct CodexEventStream {
82
- rx: Arc<Mutex<mpsc::Receiver<JsonValue>>>,
87
+ rx: Arc<Mutex<mpsc::Receiver<Result<JsonValue, String>>>>,
83
88
  }
84
89
 
85
90
  #[pymethods]
@@ -88,12 +93,13 @@ impl CodexEventStream {
88
93
  slf
89
94
  }
90
95
 
91
- fn __next__(&mut self, py: Python<'_>) -> Option<Py<PyAny>> {
96
+ fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
92
97
  // Run the blocking recv without holding the GIL
93
- 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()));
94
99
  match res {
95
- Some(v) => json_to_py(py, &v).ok(),
96
- 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),
97
103
  }
98
104
  }
99
105
  }
@@ -104,15 +110,18 @@ fn start_exec_stream(
104
110
  config_overrides: Option<Bound<'_, PyDict>>,
105
111
  load_default_config: bool,
106
112
  ) -> PyResult<CodexEventStream> {
107
- let (tx, rx) = mpsc::channel::<JsonValue>();
113
+ let (tx, rx) = mpsc::channel::<Result<JsonValue, String>>();
108
114
 
109
115
  // Build a pure-Rust Config on the Python thread
110
116
  let config = build_config(config_overrides, load_default_config).map_err(to_py)?;
111
117
  let prompt_clone = prompt.clone();
112
118
 
113
119
  thread::spawn(move || {
114
- if let Err(e) = run_exec_stream_impl(prompt_clone, config, tx) {
115
- 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}");
116
125
  }
117
126
  });
118
127
  Ok(CodexEventStream {
@@ -169,10 +178,11 @@ fn json_to_py(py: Python<'_>, v: &JsonValue) -> PyResult<Py<PyAny>> {
169
178
  Ok(obj)
170
179
  }
171
180
 
172
- fn build_config(
173
- overrides: Option<Bound<'_, PyDict>>,
174
- load_default_config: bool,
175
- ) -> Result<Config> {
181
+ fn build_config(overrides: Option<Bound<'_, PyDict>>, load_default_config: bool) -> Result<Config> {
182
+ // Match CLI behavior: import env vars from ~/.codex/.env (if present)
183
+ // before reading config/auth so OPENAI_API_KEY and friends are visible.
184
+ // Security: filter out CODEX_* variables just like the CLI does.
185
+ load_dotenv();
176
186
  let mut overrides_struct = ConfigOverrides::default();
177
187
  let mut cli_overrides: Vec<(String, TomlValue)> = Vec::new();
178
188
 
@@ -272,7 +282,10 @@ fn build_config(
272
282
 
273
283
  if load_default_config {
274
284
  // Start from built-in defaults and apply CLI + typed overrides.
275
- Ok(Config::load_with_cli_overrides(cli_overrides, overrides_struct)?)
285
+ Ok(Config::load_with_cli_overrides(
286
+ cli_overrides,
287
+ overrides_struct,
288
+ )?)
276
289
  } else {
277
290
  // Do NOT read any on-disk config. Build a TOML value purely from CLI-style overrides
278
291
  // and then apply the strongly-typed overrides on top. We still resolve CODEX_HOME to
@@ -287,7 +300,38 @@ fn build_config(
287
300
 
288
301
  let root_value = TomlValue::Table(base_tbl);
289
302
  let cfg: ConfigToml = root_value.try_into().map_err(|e| anyhow::anyhow!(e))?;
290
- 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
+ )?)
308
+ }
309
+ }
310
+
311
+ const ILLEGAL_ENV_VAR_PREFIX: &str = "CODEX_";
312
+
313
+ /// Load env vars from ~/.codex/.env, filtering out any keys that start with
314
+ /// CODEX_ (reserved for internal use). This mirrors the behavior in the
315
+ /// `codex-arg0` crate used by the CLI so python users get the same DX.
316
+ fn load_dotenv() {
317
+ if let Ok(codex_home) = find_codex_home() {
318
+ let env_path = codex_home.join(".env");
319
+ if let Ok(iter) = dotenvy::from_path_iter(env_path) {
320
+ set_filtered(iter);
321
+ }
322
+ }
323
+ }
324
+
325
+ /// Helper to set vars from a dotenvy iterator while filtering out `CODEX_` keys.
326
+ fn set_filtered<I>(iter: I)
327
+ where
328
+ I: IntoIterator<Item = Result<(String, String), dotenvy::Error>>,
329
+ {
330
+ for (key, value) in iter.into_iter().flatten() {
331
+ if !key.to_ascii_uppercase().starts_with(ILLEGAL_ENV_VAR_PREFIX) {
332
+ // Safe to modify env here – we do it up front before we spawn runtimes/threads.
333
+ unsafe { std::env::set_var(&key, &value) };
334
+ }
291
335
  }
292
336
  }
293
337
 
@@ -394,12 +438,19 @@ fn insert_parts(current: &mut TomlTable, parts: &[&str], val: TomlValue) {
394
438
  }
395
439
  }
396
440
 
397
- 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<()> {
398
446
  let rt = tokio::runtime::Runtime::new()?;
399
447
  rt.block_on(async move {
400
- let conversation_manager = ConversationManager::new(AuthManager::shared(
401
- config.codex_home.clone(),
402
- ));
448
+ let conversation_manager = match std::env::var("OPENAI_API_KEY") {
449
+ Ok(val) if !val.trim().is_empty() => {
450
+ ConversationManager::with_auth(CodexAuth::from_api_key(&val))
451
+ }
452
+ _ => ConversationManager::new(AuthManager::shared(config.codex_home.clone())),
453
+ };
403
454
  let new_conv = conversation_manager.new_conversation(config).await?;
404
455
  let conversation = new_conv.conversation.clone();
405
456
 
@@ -415,15 +466,20 @@ fn run_exec_stream_impl(prompt: String, config: Config, tx: mpsc::Sender<JsonVal
415
466
  Ok(ev) => {
416
467
  let is_shutdown = matches!(ev.msg, EventMsg::ShutdownComplete);
417
468
  let is_complete = matches!(ev.msg, EventMsg::TaskComplete(_));
418
- 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
+ }
419
473
  if is_complete {
420
- let _ = conversation.submit(codex_core::protocol::Op::Shutdown).await;
474
+ let _ = conversation
475
+ .submit(codex_core::protocol::Op::Shutdown)
476
+ .await;
421
477
  }
422
478
  if is_shutdown {
423
479
  break;
424
480
  }
425
481
  }
426
- Err(_) => break,
482
+ Err(err) => return Err(err.into()),
427
483
  }
428
484
  }
429
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