chatgpt_codex: Support reasoning and gpt-5.4 (#7941)
Signed-off-by: alexyao2015 <alexyao2015@users.noreply.github.com> Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: alexyao2015 <alexyao2015@users.noreply.github.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -22,6 +22,7 @@ use goose::config::{
|
|||||||
use goose::model::ModelConfig;
|
use goose::model::ModelConfig;
|
||||||
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
||||||
use goose::providers::base::ConfigKey;
|
use goose::providers::base::ConfigKey;
|
||||||
|
use goose::providers::chatgpt_codex::reasoning_levels_for_model;
|
||||||
use goose::providers::formats::anthropic::supports_adaptive_thinking;
|
use goose::providers::formats::anthropic::supports_adaptive_thinking;
|
||||||
use goose::providers::provider_test::test_provider_configuration;
|
use goose::providers::provider_test::test_provider_configuration;
|
||||||
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
||||||
@@ -809,6 +810,26 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider_name == "chatgpt_codex" {
|
||||||
|
let valid_levels = reasoning_levels_for_model(&model);
|
||||||
|
if !valid_levels.is_empty() {
|
||||||
|
let mut select = cliclack::select("Select reasoning effort level:");
|
||||||
|
for &level in valid_levels {
|
||||||
|
let description = match level {
|
||||||
|
"low" => "Low - Fast responses with lighter reasoning",
|
||||||
|
"medium" => "Medium - Balances speed and reasoning depth for everyday tasks",
|
||||||
|
"high" => "High - Greater reasoning depth for complex problems",
|
||||||
|
"xhigh" => "Extra High - Extra high reasoning depth for complex problems",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
select = select.item(level, description, "");
|
||||||
|
}
|
||||||
|
select = select.initial_value("medium");
|
||||||
|
let effort: &str = select.interact()?;
|
||||||
|
config.set_chatgpt_codex_reasoning_effort(effort.to_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test the configuration
|
// Test the configuration
|
||||||
let spin = spinner();
|
let spin = spinner();
|
||||||
spin.start("Checking your configuration...");
|
spin.start("Checking your configuration...");
|
||||||
|
|||||||
@@ -1012,6 +1012,7 @@ config_value!(CODEX_COMMAND, String, "codex");
|
|||||||
config_value!(CODEX_REASONING_EFFORT, String, "high");
|
config_value!(CODEX_REASONING_EFFORT, String, "high");
|
||||||
config_value!(CODEX_ENABLE_SKILLS, String, "true");
|
config_value!(CODEX_ENABLE_SKILLS, String, "true");
|
||||||
config_value!(CODEX_SKIP_GIT_CHECK, String, "false");
|
config_value!(CODEX_SKIP_GIT_CHECK, String, "false");
|
||||||
|
config_value!(CHATGPT_CODEX_REASONING_EFFORT, String, "medium");
|
||||||
|
|
||||||
config_value!(GOOSE_SEARCH_PATHS, Vec<String>);
|
config_value!(GOOSE_SEARCH_PATHS, Vec<String>);
|
||||||
config_value!(GOOSE_MODE, GooseMode);
|
config_value!(GOOSE_MODE, GooseMode);
|
||||||
|
|||||||
@@ -46,17 +46,56 @@ const HTML_AUTO_CLOSE_TIMEOUT_MS: u64 = 2000;
|
|||||||
|
|
||||||
const CHATGPT_CODEX_PROVIDER_NAME: &str = "chatgpt_codex";
|
const CHATGPT_CODEX_PROVIDER_NAME: &str = "chatgpt_codex";
|
||||||
pub const CHATGPT_CODEX_DEFAULT_MODEL: &str = "gpt-5.3-codex";
|
pub const CHATGPT_CODEX_DEFAULT_MODEL: &str = "gpt-5.3-codex";
|
||||||
pub const CHATGPT_CODEX_KNOWN_MODELS: &[&str] = &[
|
|
||||||
"gpt-5.4",
|
#[derive(Debug)]
|
||||||
"gpt-5.3-codex",
|
pub struct ChatGptCodexModelAttrs {
|
||||||
"gpt-5.2-codex",
|
pub name: &'static str,
|
||||||
"gpt-5.1-codex",
|
pub reasoning_levels: &'static [&'static str],
|
||||||
"gpt-5.1-codex-mini",
|
}
|
||||||
"gpt-5.1-codex-max",
|
|
||||||
|
pub const CHATGPT_CODEX_KNOWN_MODELS: &[ChatGptCodexModelAttrs] = &[
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.4",
|
||||||
|
reasoning_levels: &["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.3-codex",
|
||||||
|
reasoning_levels: &["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.2-codex",
|
||||||
|
reasoning_levels: &["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.1-codex",
|
||||||
|
reasoning_levels: &["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.1-codex-mini",
|
||||||
|
reasoning_levels: &["medium", "high"],
|
||||||
|
},
|
||||||
|
ChatGptCodexModelAttrs {
|
||||||
|
name: "gpt-5.1-codex-max",
|
||||||
|
reasoning_levels: &["low", "medium", "high", "xhigh"],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const CHATGPT_CODEX_DOC_URL: &str = "https://openai.com/chatgpt";
|
const CHATGPT_CODEX_DOC_URL: &str = "https://openai.com/chatgpt";
|
||||||
|
|
||||||
|
const DEFAULT_REASONING_LEVELS: &[&str] = &["medium", "high"];
|
||||||
|
|
||||||
|
pub fn reasoning_levels_for_model(model_name: &str) -> &'static [&'static str] {
|
||||||
|
CHATGPT_CODEX_KNOWN_MODELS
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.name == model_name)
|
||||||
|
.map(|m| m.reasoning_levels)
|
||||||
|
.unwrap_or(DEFAULT_REASONING_LEVELS)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn known_model_names() -> Vec<&'static str> {
|
||||||
|
CHATGPT_CODEX_KNOWN_MODELS.iter().map(|m| m.name).collect()
|
||||||
|
}
|
||||||
|
|
||||||
const GPT_53_CODEX_TOOL_PREAMBLE: &str = "\
|
const GPT_53_CODEX_TOOL_PREAMBLE: &str = "\
|
||||||
You are a coding agent. You have access to tools to accomplish tasks. \
|
You are a coding agent. You have access to tools to accomplish tasks. \
|
||||||
Always use your tools to fulfill requests - do not just describe what you would do. \
|
Always use your tools to fulfill requests - do not just describe what you would do. \
|
||||||
@@ -182,6 +221,26 @@ fn build_input_items(messages: &[Message]) -> Result<Vec<Value>> {
|
|||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_reasoning_effort(model_name: &str) -> String {
|
||||||
|
let config = crate::config::Config::global();
|
||||||
|
let effort = config
|
||||||
|
.get_chatgpt_codex_reasoning_effort()
|
||||||
|
.map(String::from)
|
||||||
|
.unwrap_or_else(|_| "medium".to_string());
|
||||||
|
|
||||||
|
let valid_levels = reasoning_levels_for_model(model_name);
|
||||||
|
if valid_levels.contains(&effort.as_str()) {
|
||||||
|
effort
|
||||||
|
} else {
|
||||||
|
tracing::warn!(
|
||||||
|
"Invalid CHATGPT_CODEX_REASONING_EFFORT '{}' for model '{}', using 'medium'",
|
||||||
|
effort,
|
||||||
|
model_name
|
||||||
|
);
|
||||||
|
"medium".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_codex_request(
|
fn create_codex_request(
|
||||||
model_config: &ModelConfig,
|
model_config: &ModelConfig,
|
||||||
system: &str,
|
system: &str,
|
||||||
@@ -189,6 +248,7 @@ fn create_codex_request(
|
|||||||
tools: &[Tool],
|
tools: &[Tool],
|
||||||
) -> Result<Value> {
|
) -> Result<Value> {
|
||||||
let input_items = build_input_items(messages)?;
|
let input_items = build_input_items(messages)?;
|
||||||
|
let reasoning_effort = get_reasoning_effort(&model_config.model_name);
|
||||||
|
|
||||||
let instructions = match model_config.model_name.as_str() {
|
let instructions = match model_config.model_name.as_str() {
|
||||||
"gpt-5.3-codex" => format!("{GPT_53_CODEX_TOOL_PREAMBLE}\n\n{system}"),
|
"gpt-5.3-codex" => format!("{GPT_53_CODEX_TOOL_PREAMBLE}\n\n{system}"),
|
||||||
@@ -199,6 +259,7 @@ fn create_codex_request(
|
|||||||
"model": model_config.model_name,
|
"model": model_config.model_name,
|
||||||
"input": input_items,
|
"input": input_items,
|
||||||
"store": false,
|
"store": false,
|
||||||
|
"reasoning": {"effort": reasoning_effort},
|
||||||
"instructions": instructions,
|
"instructions": instructions,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -871,7 +932,7 @@ impl ProviderDef for ChatGptCodexProvider {
|
|||||||
"ChatGPT Codex",
|
"ChatGPT Codex",
|
||||||
"Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models via OAuth",
|
"Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models via OAuth",
|
||||||
CHATGPT_CODEX_DEFAULT_MODEL,
|
CHATGPT_CODEX_DEFAULT_MODEL,
|
||||||
CHATGPT_CODEX_KNOWN_MODELS.to_vec(),
|
known_model_names(),
|
||||||
CHATGPT_CODEX_DOC_URL,
|
CHATGPT_CODEX_DOC_URL,
|
||||||
vec![ConfigKey::new_oauth(
|
vec![ConfigKey::new_oauth(
|
||||||
"CHATGPT_CODEX_TOKEN",
|
"CHATGPT_CODEX_TOKEN",
|
||||||
@@ -944,10 +1005,7 @@ impl Provider for ChatGptCodexProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
|
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
|
||||||
Ok(CHATGPT_CODEX_KNOWN_MODELS
|
Ok(known_model_names().into_iter().map(String::from).collect())
|
||||||
.iter()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,4 +1290,26 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(claims.chatgpt_account_id.as_deref(), Some("account-1"));
|
assert_eq!(claims.chatgpt_account_id.as_deref(), Some("account-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case("unknown-model", &["medium", "high"]; "unknown model gets default reasoning levels")]
|
||||||
|
fn test_reasoning_levels_for_model(model: &str, expected: &[&str]) {
|
||||||
|
assert_eq!(reasoning_levels_for_model(model), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gpt53_preamble_injected() {
|
||||||
|
let model = ModelConfig::new("gpt-5.3-codex").unwrap();
|
||||||
|
let payload = create_codex_request(&model, "system prompt", &[], &[]).unwrap();
|
||||||
|
let instructions = payload["instructions"].as_str().unwrap();
|
||||||
|
assert!(instructions.contains(GPT_53_CODEX_TOOL_PREAMBLE));
|
||||||
|
assert!(instructions.contains("system prompt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_other_models_no_preamble() {
|
||||||
|
let model = ModelConfig::new("gpt-5.4").unwrap();
|
||||||
|
let payload = create_codex_request(&model, "system prompt", &[], &[]).unwrap();
|
||||||
|
let instructions = payload["instructions"].as_str().unwrap();
|
||||||
|
assert_eq!(instructions, "system prompt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user