Add gpt-5.3-codex model and make it the default codex alias

- Add gpt-5.3-codex to catalog (API model, 1047576 context, 128K output)
- Move "codex" alias from gpt-5.2-codex to gpt-5.3-codex
- Add e2e integration test for gpt-5.3-codex via OpenAI API
- Remove gpt-5.3-codex-spark (not yet available)
- Empty CLI_ONLY_MODELS list and update related tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-27 21:40:01 -05:00
parent bb5f9899ed
commit 2750da01c6
5 changed files with 43 additions and 40 deletions

View file

@ -12,7 +12,7 @@ use crate::handler::codergen::{CodergenBackend, CodergenResult};
use crate::outcome::StageUsage;
/// Models that are only available through CLI tools (not via API).
const CLI_ONLY_MODELS: &[&str] = &["gpt-5.3-codex-spark"];
const CLI_ONLY_MODELS: &[&str] = &[];
/// Returns true if the given model is only available through a CLI tool.
#[must_use]
@ -410,9 +410,9 @@ mod tests {
#[test]
fn cli_command_for_codex() {
let cmd = cli_command_for_provider("openai", "gpt-5.3-codex-spark", "/tmp/prompt.txt");
let cmd = cli_command_for_provider("openai", "gpt-5.3-codex", "/tmp/prompt.txt");
assert!(cmd.starts_with("codex exec --json --full-auto"));
assert!(cmd.contains("-m gpt-5.3-codex-spark"));
assert!(cmd.contains("-m gpt-5.3-codex"));
assert!(cmd.ends_with("< /tmp/prompt.txt"));
}
@ -455,17 +455,9 @@ mod tests {
// -- Cycle 2: is_cli_only_model --
#[test]
fn codex_spark_is_cli_only() {
assert!(is_cli_only_model("gpt-5.3-codex-spark"));
}
#[test]
fn claude_opus_is_not_cli_only() {
fn no_models_are_currently_cli_only() {
assert!(!is_cli_only_model("gpt-5.3-codex"));
assert!(!is_cli_only_model("claude-opus-4-6"));
}
#[test]
fn gemini_is_not_cli_only() {
assert!(!is_cli_only_model("gemini-3.1-pro-preview"));
}
@ -583,22 +575,6 @@ mod tests {
assert!(router.should_use_cli(&node));
}
#[test]
fn router_uses_cli_for_cli_only_model() {
let mut node = Node::new("test");
node.attrs.insert(
"llm_model".to_string(),
AttrValue::String("gpt-5.3-codex-spark".to_string()),
);
let cli_backend = CliBackend::new("model".into(), "openai".into());
let router = BackendRouter::new(
Box::new(StubBackend),
cli_backend,
);
assert!(router.should_use_cli(&node));
}
#[test]
fn router_uses_api_by_default() {
let node = Node::new("test");

View file

@ -7401,7 +7401,7 @@ async fn cli_backend_run_with_codex_provider() {
let codex_output = "{\"type\":\"item.completed\",\"item\":{\"id\":\"item_0\",\"type\":\"agent_message\",\"text\":\"Implemented the feature.\"}}\n{\"type\":\"turn.completed\",\"usage\":{\"input_tokens\":300,\"output_tokens\":150}}";
let test_env = Arc::new(CliTestEnv::new(codex_output));
let env: Arc<dyn agent::ExecutionEnvironment> = test_env.clone();
let backend = CliBackend::new("gpt-5.3-codex-spark".into(), "openai".into());
let backend = CliBackend::new("gpt-5.3-codex".into(), "openai".into());
let node = Node::new("implement");
let context = Context::new();
@ -7417,7 +7417,7 @@ async fn cli_backend_run_with_codex_provider() {
let commands = test_env.recorded_commands();
let cli_cmd = commands.iter().find(|c| c.contains("codex")).expect("should call codex CLI");
assert!(cli_cmd.contains("exec --json"), "should use exec mode");
assert!(cli_cmd.contains("gpt-5.3-codex-spark"), "should use correct model");
assert!(cli_cmd.contains("gpt-5.3-codex"), "should use correct model");
match result {
CodergenResult::Text { text, usage, .. } => {
@ -7536,7 +7536,7 @@ async fn cli_backend_run_uses_node_provider_override() {
let mut node = Node::new("step");
node.attrs.insert("llm_provider".to_string(), AttrValue::String("openai".to_string()));
node.attrs.insert("llm_model".to_string(), AttrValue::String("gpt-5.3-codex-spark".to_string()));
node.attrs.insert("llm_model".to_string(), AttrValue::String("gpt-5.3-codex".to_string()));
let context = Context::new();
let emitter = Arc::new(EventEmitter::new());
@ -7549,7 +7549,7 @@ async fn cli_backend_run_uses_node_provider_override() {
let commands = test_env.recorded_commands();
let cli_cmd = commands.iter().find(|c| c.contains("codex")).expect("should call codex based on provider override");
assert!(cli_cmd.contains("gpt-5.3-codex-spark"));
assert!(cli_cmd.contains("gpt-5.3-codex"));
}
#[tokio::test]
@ -7640,16 +7640,16 @@ async fn backend_router_delegates_to_api_for_normal_node() {
}
#[tokio::test]
async fn backend_router_delegates_to_cli_for_cli_only_model() {
async fn backend_router_delegates_to_cli_for_backend_attr() {
let codex_output = "{\"type\":\"item.completed\",\"item\":{\"id\":\"item_0\",\"type\":\"agent_message\",\"text\":\"Codex did it\"}}\n{\"type\":\"turn.completed\",\"usage\":{\"input_tokens\":10,\"output_tokens\":5}}";
let env: Arc<dyn agent::ExecutionEnvironment> = Arc::new(CliTestEnv::new(codex_output));
let api_backend = Box::new(MockCodergenBackend);
let cli = CliBackend::new("gpt-5.3-codex-spark".into(), "openai".into());
let cli = CliBackend::new("gpt-5.3-codex".into(), "openai".into());
let router = BackendRouter::new(api_backend, cli);
let mut node = Node::new("codex_step");
node.attrs.insert("llm_model".to_string(), AttrValue::String("gpt-5.3-codex-spark".to_string()));
node.attrs.insert("backend".to_string(), AttrValue::String("cli".to_string()));
node.attrs.insert("llm_provider".to_string(), AttrValue::String("openai".to_string()));
let context = Context::new();
@ -7663,7 +7663,7 @@ async fn backend_router_delegates_to_cli_for_cli_only_model() {
match result {
CodergenResult::Text { text, .. } => {
assert_eq!(text, "Codex did it", "should route to CLI backend for CLI-only model");
assert_eq!(text, "Codex did it", "should route to CLI backend for backend=cli");
}
CodergenResult::Full(_) => panic!("expected Text result"),
}

View file

@ -75,6 +75,19 @@
"supports_reasoning": true,
"input_cost_per_million": null,
"output_cost_per_million": null,
"aliases": []
},
{
"id": "gpt-5.3-codex",
"provider": "openai",
"display_name": "GPT-5.3 Codex",
"context_window": 1047576,
"max_output": 128000,
"supports_tools": true,
"supports_vision": true,
"supports_reasoning": true,
"input_cost_per_million": null,
"output_cost_per_million": null,
"aliases": ["codex"]
},
{

View file

@ -51,7 +51,7 @@ mod tests {
assert_eq!(info.id, "claude-sonnet-4-5");
let info = get_model_info("codex").unwrap();
assert_eq!(info.id, "gpt-5.2-codex");
assert_eq!(info.id, "gpt-5.3-codex");
}
#[test]
@ -62,7 +62,7 @@ mod tests {
#[test]
fn list_models_all() {
let models = list_models(None);
assert_eq!(models.len(), 8);
assert_eq!(models.len(), 9);
}
#[test]
@ -72,7 +72,7 @@ mod tests {
assert!(anthropic.iter().all(|m| m.provider == "anthropic"));
let openai = list_models(Some("openai"));
assert_eq!(openai.len(), 3);
assert_eq!(openai.len(), 4);
let gemini = list_models(Some("gemini"));
assert_eq!(gemini.len(), 2);

View file

@ -52,6 +52,20 @@ async fn openai_complete() {
assert_eq!(response.provider, "openai");
}
#[tokio::test]
#[ignore = "requires OPENAI_API_KEY"]
async fn openai_gpt_5_3_codex_complete() {
let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set");
let adapter = OpenAiAdapter::new(api_key);
let request = make_request("gpt-5.3-codex");
let response = adapter.complete(&request).await.unwrap();
assert!(!response.text().is_empty(), "response text should not be empty");
assert!(response.usage.input_tokens > 0);
assert!(response.usage.output_tokens > 0);
assert_eq!(response.provider, "openai");
}
#[tokio::test]
#[ignore = "requires GEMINI_API_KEY"]
async fn gemini_complete() {