mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
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::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
||||
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::provider_test::test_provider_configuration;
|
||||
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
|
||||
let spin = spinner();
|
||||
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_ENABLE_SKILLS, String, "true");
|
||||
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_MODE, GooseMode);
|
||||
|
||||
@@ -46,17 +46,56 @@ const HTML_AUTO_CLOSE_TIMEOUT_MS: u64 = 2000;
|
||||
|
||||
const CHATGPT_CODEX_PROVIDER_NAME: &str = "chatgpt_codex";
|
||||
pub const CHATGPT_CODEX_DEFAULT_MODEL: &str = "gpt-5.3-codex";
|
||||
pub const CHATGPT_CODEX_KNOWN_MODELS: &[&str] = &[
|
||||
"gpt-5.4",
|
||||
"gpt-5.3-codex",
|
||||
"gpt-5.2-codex",
|
||||
"gpt-5.1-codex",
|
||||
"gpt-5.1-codex-mini",
|
||||
"gpt-5.1-codex-max",
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChatGptCodexModelAttrs {
|
||||
pub name: &'static str,
|
||||
pub reasoning_levels: &'static [&'static str],
|
||||
}
|
||||
|
||||
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 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 = "\
|
||||
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. \
|
||||
@@ -182,6 +221,26 @@ fn build_input_items(messages: &[Message]) -> Result<Vec<Value>> {
|
||||
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(
|
||||
model_config: &ModelConfig,
|
||||
system: &str,
|
||||
@@ -189,6 +248,7 @@ fn create_codex_request(
|
||||
tools: &[Tool],
|
||||
) -> Result<Value> {
|
||||
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() {
|
||||
"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,
|
||||
"input": input_items,
|
||||
"store": false,
|
||||
"reasoning": {"effort": reasoning_effort},
|
||||
"instructions": instructions,
|
||||
});
|
||||
|
||||
@@ -871,7 +932,7 @@ impl ProviderDef for ChatGptCodexProvider {
|
||||
"ChatGPT Codex",
|
||||
"Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models via OAuth",
|
||||
CHATGPT_CODEX_DEFAULT_MODEL,
|
||||
CHATGPT_CODEX_KNOWN_MODELS.to_vec(),
|
||||
known_model_names(),
|
||||
CHATGPT_CODEX_DOC_URL,
|
||||
vec![ConfigKey::new_oauth(
|
||||
"CHATGPT_CODEX_TOKEN",
|
||||
@@ -944,10 +1005,7 @@ impl Provider for ChatGptCodexProvider {
|
||||
}
|
||||
|
||||
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
|
||||
Ok(CHATGPT_CODEX_KNOWN_MODELS
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect())
|
||||
Ok(known_model_names().into_iter().map(String::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1232,4 +1290,26 @@ mod tests {
|
||||
|
||||
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