mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
add client-side ModelInfo overrides
This commit is contained in:
@@ -57,6 +57,7 @@ use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::openai_models::ModelInfoPatch;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_rmcp_client::OAuthCredentialsStoreMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -317,6 +318,9 @@ pub struct Config {
|
||||
/// Optional override to force-enable reasoning summaries for the configured model.
|
||||
pub model_supports_reasoning_summaries: Option<bool>,
|
||||
|
||||
/// Per-model metadata overrides keyed by model slug.
|
||||
pub model_info_overrides: HashMap<String, ModelInfoPatch>,
|
||||
|
||||
/// Optional verbosity control for GPT-5 models (Responses API `text.verbosity`).
|
||||
pub model_verbosity: Option<Verbosity>,
|
||||
|
||||
@@ -975,6 +979,10 @@ pub struct ConfigToml {
|
||||
/// Override to force-enable reasoning summaries for the configured model.
|
||||
pub model_supports_reasoning_summaries: Option<bool>,
|
||||
|
||||
/// Per-model metadata overrides keyed by model slug.
|
||||
#[serde(default)]
|
||||
pub model_info_overrides: HashMap<String, ModelInfoPatch>,
|
||||
|
||||
/// Optionally specify a personality for the model
|
||||
pub personality: Option<Personality>,
|
||||
|
||||
@@ -1766,6 +1774,7 @@ impl Config {
|
||||
.or(cfg.model_reasoning_summary)
|
||||
.unwrap_or_default(),
|
||||
model_supports_reasoning_summaries: cfg.model_supports_reasoning_summaries,
|
||||
model_info_overrides: cfg.model_info_overrides,
|
||||
model_verbosity: config_profile.model_verbosity.or(cfg.model_verbosity),
|
||||
chatgpt_base_url: config_profile
|
||||
.chatgpt_base_url
|
||||
@@ -4054,6 +4063,7 @@ model_verbosity = "high"
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
model_reasoning_summary: ReasoningSummary::Detailed,
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_info_overrides: HashMap::new(),
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
@@ -4160,6 +4170,7 @@ model_verbosity = "high"
|
||||
model_reasoning_effort: None,
|
||||
model_reasoning_summary: ReasoningSummary::default(),
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_info_overrides: HashMap::new(),
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
@@ -4264,6 +4275,7 @@ model_verbosity = "high"
|
||||
model_reasoning_effort: None,
|
||||
model_reasoning_summary: ReasoningSummary::default(),
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_info_overrides: HashMap::new(),
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
@@ -4354,6 +4366,7 @@ model_verbosity = "high"
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
model_reasoning_summary: ReasoningSummary::Detailed,
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_info_overrides: HashMap::new(),
|
||||
model_verbosity: Some(Verbosity::High),
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
|
||||
@@ -134,17 +134,19 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
// todo(aibrahim): look if we can tighten it to pub(crate)
|
||||
/// Look up model metadata, applying remote overrides and config adjustments.
|
||||
/// Look up model metadata, applying remote overrides and config adjustments, as well as client overrides.
|
||||
pub async fn get_model_info(&self, model: &str, config: &Config) -> ModelInfo {
|
||||
let remote = self
|
||||
.find_remote_model_by_longest_prefix(model, config)
|
||||
.await;
|
||||
let model = if let Some(remote) = remote {
|
||||
let model_info = if let Some(remote) = remote {
|
||||
remote
|
||||
} else {
|
||||
model_info::model_info_from_slug(model)
|
||||
};
|
||||
model_info::with_config_overrides(model, config)
|
||||
|
||||
let model_info = model_info::with_model_info_patches(model_info, model, config);
|
||||
model_info::with_config_overrides(model_info, config)
|
||||
}
|
||||
|
||||
async fn find_remote_model_by_longest_prefix(
|
||||
@@ -369,11 +371,13 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Build `ModelInfo` without consulting remote state or cache.
|
||||
pub(crate) fn construct_model_info_offline_for_tests(
|
||||
model: &str,
|
||||
config: &Config,
|
||||
) -> ModelInfo {
|
||||
model_info::with_config_overrides(model_info::model_info_from_slug(model), config)
|
||||
///
|
||||
/// This follows the same override precedence as `get_model_info`:
|
||||
/// per-model `model_info_overrides` are applied first, then top-level config overrides.
|
||||
pub(crate) fn construct_model_info_offline_for_tests(model: &str, config: &Config) -> ModelInfo {
|
||||
let model_info = model_info::model_info_from_slug(model);
|
||||
let model_info = model_info::with_model_info_patches(model_info, model, config);
|
||||
model_info::with_config_overrides(model_info, config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,102 @@ const LOCAL_FRIENDLY_TEMPLATE: &str =
|
||||
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
|
||||
pub(crate) fn with_model_info_patch(
|
||||
mut model: ModelInfo,
|
||||
model_key: &str,
|
||||
config: &Config,
|
||||
) -> ModelInfo {
|
||||
let Some(model_info_patch) = config.model_info_overrides.get(model_key) else {
|
||||
return model;
|
||||
};
|
||||
|
||||
model.slug = model_key.to_string();
|
||||
|
||||
if let Some(display_name) = &model_info_patch.display_name {
|
||||
model.display_name = display_name.clone();
|
||||
}
|
||||
if let Some(description) = &model_info_patch.description {
|
||||
model.description = Some(description.clone());
|
||||
}
|
||||
if let Some(default_reasoning_level) = model_info_patch.default_reasoning_level {
|
||||
model.default_reasoning_level = Some(default_reasoning_level);
|
||||
}
|
||||
if let Some(supported_reasoning_levels) = &model_info_patch.supported_reasoning_levels {
|
||||
model.supported_reasoning_levels = supported_reasoning_levels.clone();
|
||||
}
|
||||
if let Some(shell_type) = model_info_patch.shell_type {
|
||||
model.shell_type = shell_type;
|
||||
}
|
||||
if let Some(visibility) = model_info_patch.visibility {
|
||||
model.visibility = visibility;
|
||||
}
|
||||
if let Some(supported_in_api) = model_info_patch.supported_in_api {
|
||||
model.supported_in_api = supported_in_api;
|
||||
}
|
||||
if let Some(priority) = model_info_patch.priority {
|
||||
model.priority = priority;
|
||||
}
|
||||
if let Some(upgrade) = &model_info_patch.upgrade {
|
||||
model.upgrade = Some(upgrade.clone());
|
||||
}
|
||||
if let Some(base_instructions) = &model_info_patch.base_instructions {
|
||||
model.base_instructions = base_instructions.clone();
|
||||
}
|
||||
if let Some(model_messages) = &model_info_patch.model_messages {
|
||||
model.model_messages = Some(model_messages.clone());
|
||||
}
|
||||
if let Some(supports_reasoning_summaries) = model_info_patch.supports_reasoning_summaries {
|
||||
model.supports_reasoning_summaries = supports_reasoning_summaries;
|
||||
}
|
||||
if let Some(support_verbosity) = model_info_patch.support_verbosity {
|
||||
model.support_verbosity = support_verbosity;
|
||||
}
|
||||
if let Some(default_verbosity) = model_info_patch.default_verbosity {
|
||||
model.default_verbosity = Some(default_verbosity);
|
||||
}
|
||||
if let Some(apply_patch_tool_type) = &model_info_patch.apply_patch_tool_type {
|
||||
model.apply_patch_tool_type = Some(apply_patch_tool_type.clone());
|
||||
}
|
||||
if let Some(truncation_policy) = model_info_patch.truncation_policy {
|
||||
model.truncation_policy = truncation_policy;
|
||||
}
|
||||
if let Some(supports_parallel_tool_calls) = model_info_patch.supports_parallel_tool_calls {
|
||||
model.supports_parallel_tool_calls = supports_parallel_tool_calls;
|
||||
}
|
||||
if let Some(context_window) = model_info_patch.context_window {
|
||||
model.context_window = Some(context_window);
|
||||
}
|
||||
if let Some(auto_compact_token_limit) = model_info_patch.auto_compact_token_limit {
|
||||
model.auto_compact_token_limit = Some(auto_compact_token_limit);
|
||||
}
|
||||
if let Some(effective_context_window_percent) =
|
||||
model_info_patch.effective_context_window_percent
|
||||
{
|
||||
model.effective_context_window_percent = effective_context_window_percent;
|
||||
}
|
||||
if let Some(experimental_supported_tools) = &model_info_patch.experimental_supported_tools {
|
||||
model.experimental_supported_tools = experimental_supported_tools.clone();
|
||||
}
|
||||
if let Some(input_modalities) = &model_info_patch.input_modalities {
|
||||
model.input_modalities = input_modalities.clone();
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
pub(crate) fn with_model_info_patches(
|
||||
mut model_info: ModelInfo,
|
||||
requested_model: &str,
|
||||
config: &Config,
|
||||
) -> ModelInfo {
|
||||
let resolved_slug = model_info.slug.clone();
|
||||
model_info = with_model_info_patch(model_info, &resolved_slug, config);
|
||||
if requested_model != resolved_slug {
|
||||
model_info = with_model_info_patch(model_info, requested_model, config);
|
||||
}
|
||||
model_info
|
||||
}
|
||||
|
||||
pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> ModelInfo {
|
||||
if let Some(supports_reasoning_summaries) = config.model_supports_reasoning_summaries {
|
||||
model.supports_reasoning_summaries = supports_reasoning_summaries;
|
||||
|
||||
Reference in New Issue
Block a user