Skip to content

Commit 4f1b219

Browse files
committed
fix: don't load config file if not reequested
1 parent 5be4802 commit 4f1b219

5 files changed

Lines changed: 90 additions & 14 deletions

File tree

codex/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
]
3232

3333
# Package version. Kept in sync with Cargo.toml via CI before builds.
34-
__version__ = "0.2.12"
34+
__version__ = "0.2.14"

codex/api.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from dataclasses import dataclass
55

66
from .config import CodexConfig
7-
from .event import Event
7+
from .event import AnyEventMsg, Event
88
from .native import run_exec_collect as native_run_exec_collect
99
from .native import start_exec_stream as native_start_exec_stream
1010

@@ -32,7 +32,15 @@ class Conversation:
3232
def __iter__(self) -> Iterator[Event]:
3333
"""Yield `Event` objects from the native stream."""
3434
for item in self._stream:
35-
yield Event.model_validate(item)
35+
try:
36+
yield Event.model_validate(item)
37+
except Exception:
38+
ev_id = item.get("id") if isinstance(item, dict) else None
39+
msg_obj = item.get("msg") if isinstance(item, dict) else None
40+
if isinstance(msg_obj, dict) and isinstance(msg_obj.get("type"), str):
41+
yield Event(id=ev_id or "unknown", msg=AnyEventMsg(**msg_obj))
42+
else:
43+
yield Event(id=ev_id or "unknown", msg=AnyEventMsg(type="unknown"))
3644

3745

3846
@dataclass(slots=True)
@@ -88,6 +96,18 @@ def run_exec(
8896
config_overrides=config.to_dict() if config else None,
8997
load_default_config=load_default_config,
9098
)
91-
return [Event.model_validate(e) for e in events]
9299
except RuntimeError as e:
93100
raise CodexNativeError() from e
101+
102+
out: list[Event] = []
103+
for item in events:
104+
try:
105+
out.append(Event.model_validate(item))
106+
except Exception:
107+
ev_id = item.get("id") if isinstance(item, dict) else None
108+
msg_obj = item.get("msg") if isinstance(item, dict) else None
109+
if isinstance(msg_obj, dict) and isinstance(msg_obj.get("type"), str):
110+
out.append(Event(id=ev_id or "unknown", msg=AnyEventMsg(**msg_obj)))
111+
else:
112+
out.append(Event(id=ev_id or "unknown", msg=AnyEventMsg(type="unknown")))
113+
return out

codex/event.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@
66
from .protocol.types import EventMsg
77

88

9+
class AnyEventMsg(BaseModel):
10+
"""Fallback event payload that preserves the original `type` and fields.
11+
12+
Accepts any additional keys to retain upstream payloads when strict
13+
validation fails for generated models.
14+
"""
15+
16+
type: str
17+
18+
# Allow arbitrary extra fields so we don't lose information
19+
model_config = ConfigDict(extra="allow")
20+
21+
922
class Event(BaseModel):
1023
"""Protocol event envelope with typed `msg` (union of EventMsg_*)."""
1124

1225
id: str
13-
msg: EventMsg
26+
msg: EventMsg | AnyEventMsg
1427

1528
# Allow forward compatibility with additional envelope fields
1629
model_config = ConfigDict(extra="allow")

crates/codex_native/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "codex_native"
3-
version = "0.2.12"
3+
version = "0.2.14"
44
edition = "2021"
55

66
[lib]
@@ -25,8 +25,8 @@ pathdiff = "0.2"
2525

2626
# Upstream Codex crates from the monorepo (use git deps; pin to main for now)
2727
# Pin to a specific commit of the upstream Codex monorepo to avoid breaking API changes
28-
codex-core = { git = "https://github.com/openai/codex", package = "codex-core", rev = "c13c3dad" }
29-
codex-protocol = { git = "https://github.com/openai/codex", package = "codex-protocol", rev = "c13c3dad" }
28+
codex-core = { git = "https://github.com/openai/codex", package = "codex-core", branch = "main" }
29+
codex-protocol = { git = "https://github.com/openai/codex", package = "codex-protocol", branch = "main" }
3030

3131
# Tell maturin to include the Python sources from the repo root (mixed project)
3232
[package.metadata.maturin]

crates/codex_native/src/lib.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use anyhow::{Context, Result};
2-
use codex_core::config::{
3-
find_codex_home, load_config_as_toml_with_cli_overrides, Config, ConfigOverrides, ConfigToml,
4-
};
2+
use codex_core::config::{find_codex_home, Config, ConfigOverrides, ConfigToml};
53
use codex_core::protocol::{EventMsg, InputItem};
64
use codex_core::{AuthManager, ConversationManager};
75
// use of SandboxMode is handled within core::config; not needed here
@@ -42,7 +40,6 @@ fn run_exec_collect(
4240
async fn run_exec_impl(prompt: String, config: Config) -> Result<Vec<JsonValue>> {
4341
let conversation_manager = ConversationManager::new(AuthManager::shared(
4442
config.codex_home.clone(),
45-
config.preferred_auth_method,
4643
));
4744
let new_conv = conversation_manager.new_conversation(config).await?;
4845
let conversation = new_conv.conversation.clone();
@@ -274,10 +271,22 @@ fn build_config(
274271
}
275272

276273
if load_default_config {
274+
// Start from built-in defaults and apply CLI + typed overrides.
277275
Ok(Config::load_with_cli_overrides(cli_overrides, overrides_struct)?)
278276
} else {
277+
// Do NOT read any on-disk config. Build a TOML value purely from CLI-style overrides
278+
// and then apply the strongly-typed overrides on top. We still resolve CODEX_HOME to
279+
// pass through for paths/auth handling, but we avoid parsing a config file.
279280
let codex_home = find_codex_home()?;
280-
let cfg = load_config_as_toml_with_cli_overrides(&codex_home, cli_overrides)?;
281+
282+
// Build a base TOML value from dotted CLI overrides only (no file IO).
283+
let mut base_tbl: TomlTable = TomlTable::new();
284+
for (k, v) in cli_overrides.into_iter() {
285+
insert_dotted_toml(&mut base_tbl, &k, v);
286+
}
287+
288+
let root_value = TomlValue::Table(base_tbl);
289+
let cfg: ConfigToml = root_value.try_into().map_err(|e| anyhow::anyhow!(e))?;
281290
Ok(Config::load_from_base_config_with_overrides(cfg, overrides_struct, codex_home)?)
282291
}
283292
}
@@ -350,12 +359,46 @@ fn flatten_overrides(out: &mut Vec<(String, TomlValue)>, prefix: &str, val: Toml
350359
}
351360
}
352361

362+
/// Insert a TOML value into `tbl` at a dotted path like "a.b.c".
363+
fn insert_dotted_toml(tbl: &mut TomlTable, dotted: &str, val: TomlValue) {
364+
let parts: Vec<&str> = dotted.split('.').collect();
365+
insert_parts(tbl, &parts, val);
366+
}
367+
368+
fn insert_parts(current: &mut TomlTable, parts: &[&str], val: TomlValue) {
369+
if parts.is_empty() {
370+
return;
371+
}
372+
if parts.len() == 1 {
373+
current.insert(parts[0].to_string(), val);
374+
return;
375+
}
376+
377+
let key = parts[0].to_string();
378+
// Get or create an intermediate table at this segment.
379+
if let Some(existing) = current.get_mut(&key) {
380+
match existing {
381+
TomlValue::Table(ref mut t) => {
382+
insert_parts(t, &parts[1..], val);
383+
}
384+
_ => {
385+
let mut next = TomlTable::new();
386+
insert_parts(&mut next, &parts[1..], val);
387+
*existing = TomlValue::Table(next);
388+
}
389+
}
390+
} else {
391+
let mut next = TomlTable::new();
392+
insert_parts(&mut next, &parts[1..], val);
393+
current.insert(key, TomlValue::Table(next));
394+
}
395+
}
396+
353397
fn run_exec_stream_impl(prompt: String, config: Config, tx: mpsc::Sender<JsonValue>) -> Result<()> {
354398
let rt = tokio::runtime::Runtime::new()?;
355399
rt.block_on(async move {
356400
let conversation_manager = ConversationManager::new(AuthManager::shared(
357401
config.codex_home.clone(),
358-
config.preferred_auth_method,
359402
));
360403
let new_conv = conversation_manager.new_conversation(config).await?;
361404
let conversation = new_conv.conversation.clone();

0 commit comments

Comments
 (0)