fix: Codex PTY launch forwards profile model as argv override
- injecte model et model_reasoning_effort du profil via -c dans argv - remplace le modèle TUI stale en config.toml - aligne PTY/interactif sur le comportement déjà garanti par CodexExecSession - test unitaire codex_pty_launch_forwards_profile_model_as_config_override
This commit is contained in:
@ -1254,6 +1254,49 @@ fn projection_model(profile: &AgentProfile) -> Option<&str> {
|
|||||||
profile.model.as_deref()
|
profile.model.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_codex_profile(profile: &AgentProfile) -> bool {
|
||||||
|
matches!(profile.structured_adapter, Some(StructuredAdapter::Codex))
|
||||||
|
|| matches!(profile.projector, Some(ProjectorKey::Codex))
|
||||||
|
|| profile
|
||||||
|
.command
|
||||||
|
.rsplit(['/', '\\'])
|
||||||
|
.next()
|
||||||
|
.unwrap_or(&profile.command)
|
||||||
|
== "codex"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_codex_pty_model_overrides(profile: &AgentProfile, spec: &mut SpawnSpec) {
|
||||||
|
if !is_codex_profile(profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(model) = profile
|
||||||
|
.model
|
||||||
|
.as_deref()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|model| !model.is_empty())
|
||||||
|
{
|
||||||
|
spec.args.push("-c".to_owned());
|
||||||
|
spec.args
|
||||||
|
.push(format!("model={}", toml_string_literal(model)));
|
||||||
|
}
|
||||||
|
if let Some(effort) = profile
|
||||||
|
.model_reasoning_effort
|
||||||
|
.as_deref()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|effort| !effort.is_empty())
|
||||||
|
{
|
||||||
|
spec.args.push("-c".to_owned());
|
||||||
|
spec.args.push(format!(
|
||||||
|
"model_reasoning_effort={}",
|
||||||
|
toml_string_literal(effort)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toml_string_literal(value: &str) -> String {
|
||||||
|
serde_json::to_string(value).expect("string serialization cannot fail")
|
||||||
|
}
|
||||||
|
|
||||||
/// Launches an agent: resolve profile + context, prepare the invocation, apply
|
/// Launches an agent: resolve profile + context, prepare the invocation, apply
|
||||||
/// the context-injection plan, open a PTY at the resolved `cwd`, spawn the CLI.
|
/// the context-injection plan, open a PTY at the resolved `cwd`, spawn the CLI.
|
||||||
///
|
///
|
||||||
@ -2036,6 +2079,12 @@ impl LaunchAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PTY/interactif Codex : le TUI conserve un état `model = ...` dans son
|
||||||
|
// CODEX_HOME isolé. Passer le modèle du profil sur l'argv garantit que le
|
||||||
|
// lancement interactif respecte l'édition IdeA, comme le chemin structuré
|
||||||
|
// le fait déjà dans `CodexExecSession`.
|
||||||
|
append_codex_pty_model_overrides(&profile, &mut spec);
|
||||||
|
|
||||||
// 6. Spawn the PTY at the resolved cwd; adopt its session id everywhere.
|
// 6. Spawn the PTY at the resolved cwd; adopt its session id everywhere.
|
||||||
let handle = self.pty.spawn(spec.clone(), size).await?;
|
let handle = self.pty.spawn(spec.clone(), size).await?;
|
||||||
let session_id = handle.session_id;
|
let session_id = handle.session_id;
|
||||||
|
|||||||
@ -3842,6 +3842,60 @@ async fn codex_projection_folds_args_into_spawn_spec() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn codex_pty_launch_forwards_profile_model_as_config_override() {
|
||||||
|
let profile = codex_profile()
|
||||||
|
.with_projector(ProjectorKey::Codex)
|
||||||
|
.with_model("gpt-5.4")
|
||||||
|
.with_model_reasoning_effort("medium");
|
||||||
|
let (launch, agent, fs, pty, _s) = launch_with_projection(
|
||||||
|
profile,
|
||||||
|
Some(ContextInjectionPlan::File {
|
||||||
|
target: "AGENTS.md".to_owned(),
|
||||||
|
}),
|
||||||
|
Some(full_registry()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let run_dir = format!("/home/me/proj/.ideai/run/{}", agent.id);
|
||||||
|
let cfg_path = format!("{run_dir}{CODEX_CONFIG_REL}");
|
||||||
|
fs.seed_read(
|
||||||
|
&cfg_path,
|
||||||
|
"model = \"gpt-5.6-sol\"\n[mcp_servers.idea]\ncommand = \"idea\"\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
launch
|
||||||
|
.execute(launch_input(agent.id))
|
||||||
|
.await
|
||||||
|
.expect("launch");
|
||||||
|
|
||||||
|
let args = &pty.spawns()[0].args;
|
||||||
|
assert!(
|
||||||
|
args.windows(2)
|
||||||
|
.any(|w| w == ["-c".to_owned(), "model=\"gpt-5.4\"".to_owned()]),
|
||||||
|
"PTY Codex launch must override stale TUI model from config.toml, got {args:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.windows(2).any(|w| w
|
||||||
|
== [
|
||||||
|
"-c".to_owned(),
|
||||||
|
"model_reasoning_effort=\"medium\"".to_owned()
|
||||||
|
]),
|
||||||
|
"PTY Codex launch must also forward reasoning effort, got {args:?}"
|
||||||
|
);
|
||||||
|
let toml = String::from_utf8(
|
||||||
|
fs.writes_ending_with(CODEX_CONFIG_REL)
|
||||||
|
.last()
|
||||||
|
.expect("a codex config write")
|
||||||
|
.1
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
toml.contains("model = \"gpt-5.6-sol\""),
|
||||||
|
"the unmanaged stale TUI model may remain in config.toml; argv must take precedence: {toml}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- (5) MCP decoupling — THE key case of the lot ---------------------------
|
// ---- (5) MCP decoupling — THE key case of the lot ---------------------------
|
||||||
|
|
||||||
/// (5) A Codex profile with **no MCP capability** still gets its sandbox projected
|
/// (5) A Codex profile with **no MCP capability** still gets its sandbox projected
|
||||||
|
|||||||
Reference in New Issue
Block a user