mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Call models endpoint in models manager (#7616)
- Introduce `with_remote_overrides` and update `refresh_available_models` - Put `auth_manager` instead of `auth_mode` on `models_manager` - Remove `ShellType` and `ReasoningLevel` to use already existing structs
This commit is contained in:
committed by
GitHub
Unverified
parent
6736d1828d
commit
7b359c9c8e
@@ -5,11 +5,11 @@ use codex_api::provider::RetryConfig;
|
||||
use codex_api::provider::WireApi;
|
||||
use codex_client::ReqwestTransport;
|
||||
use codex_protocol::openai_models::ClientVersion;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::openai_models::ReasoningLevel;
|
||||
use codex_protocol::openai_models::ShellType;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use wiremock::Mock;
|
||||
@@ -55,13 +55,13 @@ async fn models_client_hits_models_endpoint() {
|
||||
slug: "gpt-test".to_string(),
|
||||
display_name: "gpt-test".to_string(),
|
||||
description: Some("desc".to_string()),
|
||||
default_reasoning_level: ReasoningLevel::Medium,
|
||||
default_reasoning_level: ReasoningEffort::Medium,
|
||||
supported_reasoning_levels: vec![
|
||||
ReasoningLevel::Low,
|
||||
ReasoningLevel::Medium,
|
||||
ReasoningLevel::High,
|
||||
ReasoningEffort::Low,
|
||||
ReasoningEffort::Medium,
|
||||
ReasoningEffort::High,
|
||||
],
|
||||
shell_type: ShellType::ShellCommand,
|
||||
shell_type: ConfigShellToolType::ShellCommand,
|
||||
visibility: ModelVisibility::List,
|
||||
minimal_client_version: ClientVersion(0, 1, 0),
|
||||
supported_in_api: true,
|
||||
|
||||
@@ -2475,6 +2475,7 @@ pub(crate) use tests::make_session_and_context_with_rx;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::CodexAuth;
|
||||
use crate::config::ConfigOverrides;
|
||||
use crate::config::ConfigToml;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
@@ -2765,12 +2766,9 @@ mod tests {
|
||||
.expect("load default test config");
|
||||
let config = Arc::new(config);
|
||||
let conversation_id = ConversationId::default();
|
||||
let auth_manager = AuthManager::shared(
|
||||
config.cwd.clone(),
|
||||
false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.get_auth_mode()));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
|
||||
let otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
|
||||
|
||||
@@ -2801,7 +2799,7 @@ mod tests {
|
||||
rollout: Mutex::new(None),
|
||||
user_shell: default_user_shell(),
|
||||
show_raw_agent_reasoning: config.show_raw_agent_reasoning,
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
auth_manager: auth_manager.clone(),
|
||||
otel_event_manager: otel_event_manager.clone(),
|
||||
models_manager: models_manager.clone(),
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
@@ -2847,12 +2845,9 @@ mod tests {
|
||||
.expect("load default test config");
|
||||
let config = Arc::new(config);
|
||||
let conversation_id = ConversationId::default();
|
||||
let auth_manager = AuthManager::shared(
|
||||
config.cwd.clone(),
|
||||
false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.get_auth_mode()));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
|
||||
let otel_event_manager =
|
||||
otel_event_manager(conversation_id, config.as_ref(), &models_manager);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ impl ConversationManager {
|
||||
conversations: Arc::new(RwLock::new(HashMap::new())),
|
||||
auth_manager: auth_manager.clone(),
|
||||
session_source,
|
||||
models_manager: Arc::new(ModelsManager::new(auth_manager.get_auth_mode())),
|
||||
models_manager: Arc::new(ModelsManager::new(auth_manager)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::types::ReasoningSummaryFormat;
|
||||
use crate::tools::handlers::apply_patch::ApplyPatchToolType;
|
||||
use crate::tools::spec::ConfigShellToolType;
|
||||
use crate::truncate::TruncationPolicy;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
|
||||
/// The `instructions` field in the payload sent to a model should always start
|
||||
/// with this content.
|
||||
@@ -83,6 +84,15 @@ impl ModelFamily {
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn with_remote_overrides(mut self, remote_models: Vec<ModelInfo>) -> Self {
|
||||
for model in remote_models {
|
||||
if model.slug == self.slug {
|
||||
self.default_reasoning_effort = Some(model.default_reasoning_level);
|
||||
self.shell_type = model.shell_type;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! model_family {
|
||||
@@ -275,3 +285,71 @@ fn derive_default_model_family(model: &str) -> ModelFamily {
|
||||
truncation_policy: TruncationPolicy::Bytes(10_000),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_protocol::openai_models::ClientVersion;
|
||||
use codex_protocol::openai_models::ModelVisibility;
|
||||
|
||||
fn remote(slug: &str, effort: ReasoningEffort, shell: ConfigShellToolType) -> ModelInfo {
|
||||
ModelInfo {
|
||||
slug: slug.to_string(),
|
||||
display_name: slug.to_string(),
|
||||
description: Some(format!("{slug} desc")),
|
||||
default_reasoning_level: effort,
|
||||
supported_reasoning_levels: vec![effort],
|
||||
shell_type: shell,
|
||||
visibility: ModelVisibility::List,
|
||||
minimal_client_version: ClientVersion(0, 1, 0),
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_overrides_apply_when_slug_matches() {
|
||||
let family = model_family!("gpt-4o-mini", "gpt-4o-mini");
|
||||
assert_ne!(family.default_reasoning_effort, Some(ReasoningEffort::High));
|
||||
|
||||
let updated = family.with_remote_overrides(vec![
|
||||
remote(
|
||||
"gpt-4o-mini",
|
||||
ReasoningEffort::High,
|
||||
ConfigShellToolType::ShellCommand,
|
||||
),
|
||||
remote(
|
||||
"other-model",
|
||||
ReasoningEffort::Low,
|
||||
ConfigShellToolType::UnifiedExec,
|
||||
),
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
updated.default_reasoning_effort,
|
||||
Some(ReasoningEffort::High)
|
||||
);
|
||||
assert_eq!(updated.shell_type, ConfigShellToolType::ShellCommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_overrides_skip_non_matching_models() {
|
||||
let family = model_family!(
|
||||
"codex-mini-latest",
|
||||
"codex-mini-latest",
|
||||
shell_type: ConfigShellToolType::Local
|
||||
);
|
||||
|
||||
let updated = family.clone().with_remote_overrides(vec![remote(
|
||||
"other",
|
||||
ReasoningEffort::High,
|
||||
ConfigShellToolType::ShellCommand,
|
||||
)]);
|
||||
|
||||
assert_eq!(
|
||||
updated.default_reasoning_effort,
|
||||
family.default_reasoning_effort
|
||||
);
|
||||
assert_eq!(updated.shell_type, family.shell_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,172 @@
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_api::ModelsClient;
|
||||
use codex_api::ReqwestTransport;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use http::HeaderMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::api_bridge::auth_provider_from_auth;
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::auth::AuthManager;
|
||||
use crate::config::Config;
|
||||
use crate::default_client::build_reqwest_client;
|
||||
use crate::error::Result as CoreResult;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::openai_models::model_family::ModelFamily;
|
||||
use crate::openai_models::model_family::find_family_for_model;
|
||||
use crate::openai_models::model_presets::builtin_model_presets;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModelsManager {
|
||||
// todo(aibrahim) merge available_models and model family creation into one struct
|
||||
pub available_models: RwLock<Vec<ModelPreset>>,
|
||||
pub remote_models: RwLock<Vec<ModelInfo>>,
|
||||
pub etag: String,
|
||||
pub auth_mode: Option<AuthMode>,
|
||||
pub auth_manager: Arc<AuthManager>,
|
||||
}
|
||||
|
||||
impl ModelsManager {
|
||||
pub fn new(auth_mode: Option<AuthMode>) -> Self {
|
||||
pub fn new(auth_manager: Arc<AuthManager>) -> Self {
|
||||
Self {
|
||||
available_models: RwLock::new(builtin_model_presets(auth_mode)),
|
||||
available_models: RwLock::new(builtin_model_presets(auth_manager.get_auth_mode())),
|
||||
remote_models: RwLock::new(Vec::new()),
|
||||
etag: String::new(),
|
||||
auth_mode,
|
||||
auth_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh_available_models(&self) {
|
||||
let models = builtin_model_presets(self.auth_mode);
|
||||
*self.available_models.write().await = models;
|
||||
// do not use this function yet. It's work in progress.
|
||||
pub async fn refresh_available_models(
|
||||
&self,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> CoreResult<Vec<ModelInfo>> {
|
||||
let auth = self.auth_manager.auth();
|
||||
let api_provider = provider.to_api_provider(auth.as_ref().map(|auth| auth.mode))?;
|
||||
let api_auth = auth_provider_from_auth(auth.clone(), provider).await?;
|
||||
let transport = ReqwestTransport::new(build_reqwest_client());
|
||||
let client = ModelsClient::new(transport, api_provider, api_auth);
|
||||
|
||||
let response = client
|
||||
.list_models(env!("CARGO_PKG_VERSION"), HeaderMap::new())
|
||||
.await
|
||||
.map_err(map_api_error)?;
|
||||
|
||||
let models = response.models;
|
||||
*self.remote_models.write().await = models.clone();
|
||||
{
|
||||
let mut available_models_guard = self.available_models.write().await;
|
||||
*available_models_guard = self.build_available_models().await;
|
||||
}
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
pub fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily {
|
||||
find_family_for_model(model).with_config_overrides(config)
|
||||
}
|
||||
|
||||
async fn build_available_models(&self) -> Vec<ModelPreset> {
|
||||
let mut available_models = self.remote_models.read().await.clone();
|
||||
available_models.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
let mut model_presets: Vec<ModelPreset> =
|
||||
available_models.into_iter().map(Into::into).collect();
|
||||
if let Some(default) = model_presets.first_mut() {
|
||||
default.is_default = true;
|
||||
}
|
||||
model_presets
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::CodexAuth;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use serde_json::json;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
|
||||
fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo {
|
||||
serde_json::from_value(json!({
|
||||
"slug": slug,
|
||||
"display_name": display,
|
||||
"description": format!("{display} desc"),
|
||||
"default_reasoning_level": "medium",
|
||||
"supported_reasoning_levels": ["low", "medium"],
|
||||
"shell_type": "shell_command",
|
||||
"visibility": "list",
|
||||
"minimal_client_version": [0, 1, 0],
|
||||
"supported_in_api": true,
|
||||
"priority": priority
|
||||
}))
|
||||
.expect("valid model")
|
||||
}
|
||||
|
||||
fn provider_for(base_url: String) -> ModelProviderInfo {
|
||||
ModelProviderInfo {
|
||||
name: "mock".into(),
|
||||
base_url: Some(base_url),
|
||||
env_key: None,
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
request_max_retries: Some(0),
|
||||
stream_max_retries: Some(0),
|
||||
stream_idle_timeout_ms: Some(5_000),
|
||||
requires_openai_auth: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_available_models_sorts_and_marks_default() {
|
||||
let server = MockServer::start().await;
|
||||
let remote_models = vec![
|
||||
remote_model("priority-low", "Low", 1),
|
||||
remote_model("priority-high", "High", 10),
|
||||
];
|
||||
let response = ModelsResponse {
|
||||
models: remote_models.clone(),
|
||||
};
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/models"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "application/json")
|
||||
.set_body_json(&response),
|
||||
)
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(auth_manager);
|
||||
let provider = provider_for(server.uri());
|
||||
|
||||
let returned = manager
|
||||
.refresh_available_models(&provider)
|
||||
.await
|
||||
.expect("refresh succeeds");
|
||||
|
||||
assert_eq!(returned, remote_models);
|
||||
let cached_remote = manager.remote_models.read().await.clone();
|
||||
assert_eq!(cached_remote, remote_models);
|
||||
|
||||
let available = manager.available_models.read().await.clone();
|
||||
assert_eq!(available.len(), 2);
|
||||
assert_eq!(available[0].model, "priority-high");
|
||||
assert!(
|
||||
available[0].is_default,
|
||||
"highest priority should be default"
|
||||
);
|
||||
assert_eq!(available[1].model, "priority-low");
|
||||
assert!(!available[1].is_default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::tools::handlers::apply_patch::ApplyPatchToolType;
|
||||
use crate::tools::handlers::apply_patch::create_apply_patch_freeform_tool;
|
||||
use crate::tools::handlers::apply_patch::create_apply_patch_json_tool;
|
||||
use crate::tools::registry::ToolRegistryBuilder;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -15,20 +16,6 @@ use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ConfigShellToolType {
|
||||
Default,
|
||||
Local,
|
||||
UnifiedExec,
|
||||
/// Do not include a shell tool by default. Useful when using Codex
|
||||
/// with tools provided exclusively provided by MCP servers. Often used
|
||||
/// with `--config base_instructions=CUSTOM_INSTRUCTIONS`
|
||||
/// to customize agent behavior.
|
||||
Disabled,
|
||||
/// Takes a command as a single string to be run in the user's default shell.
|
||||
ShellCommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ToolsConfig {
|
||||
pub shell_type: ConfigShellToolType,
|
||||
@@ -58,7 +45,7 @@ impl ToolsConfig {
|
||||
} else if features.enabled(Feature::UnifiedExec) {
|
||||
ConfigShellToolType::UnifiedExec
|
||||
} else {
|
||||
model_family.shell_type.clone()
|
||||
model_family.shell_type
|
||||
};
|
||||
|
||||
let apply_patch_tool_type = match model_family.apply_patch_tool_type {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ContentItem;
|
||||
use codex_core::LocalShellAction;
|
||||
use codex_core::LocalShellExecAction;
|
||||
@@ -71,7 +73,8 @@ async fn run_request(input: Vec<ResponseItem>) -> Value {
|
||||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use assert_matches::assert_matches;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use std::sync::Arc;
|
||||
use tracing_test::traced_test;
|
||||
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ContentItem;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::ModelProviderInfo;
|
||||
@@ -71,8 +72,9 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
|
||||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ApiKey;
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let auth_mode = auth_manager.get_auth_mode();
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
@@ -80,7 +82,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec<ResponseEvent> {
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(auth_mode),
|
||||
auth_mode,
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ContentItem;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::ModelProviderInfo;
|
||||
@@ -63,7 +65,8 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
@@ -154,7 +157,8 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
@@ -246,8 +250,8 @@ async fn responses_respects_model_family_overrides_from_config() {
|
||||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
@@ -255,7 +259,7 @@ async fn responses_respects_model_family_overrides_from_config() {
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(auth_mode),
|
||||
auth_manager.get_auth_mode(),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ContentItem;
|
||||
use codex_core::ConversationManager;
|
||||
@@ -1017,8 +1017,8 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
let config = Arc::new(config);
|
||||
|
||||
let conversation_id = ConversationId::new();
|
||||
let auth_mode = AuthMode::ChatGPT;
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(auth_mode)));
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager.clone()));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let otel_event_manager = OtelEventManager::new(
|
||||
conversation_id,
|
||||
@@ -1026,7 +1026,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
model_family.slug.as_str(),
|
||||
None,
|
||||
Some("test@test.com".to_string()),
|
||||
Some(AuthMode::ChatGPT),
|
||||
auth_manager.get_auth_mode(),
|
||||
false,
|
||||
"test".to_string(),
|
||||
);
|
||||
|
||||
@@ -86,28 +86,24 @@ pub enum ModelVisibility {
|
||||
None,
|
||||
}
|
||||
|
||||
/// Reasoning support level reported by the backend.
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema, EnumIter, Display,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ReasoningLevel {
|
||||
None,
|
||||
Minimal,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
}
|
||||
|
||||
/// Shell execution capability for a model.
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, TS, JsonSchema, EnumIter, Display,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
TS,
|
||||
JsonSchema,
|
||||
EnumIter,
|
||||
Display,
|
||||
Hash,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ShellType {
|
||||
pub enum ConfigShellToolType {
|
||||
Default,
|
||||
Local,
|
||||
UnifiedExec,
|
||||
@@ -126,9 +122,9 @@ pub struct ModelInfo {
|
||||
pub display_name: String,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
pub default_reasoning_level: ReasoningLevel,
|
||||
pub supported_reasoning_levels: Vec<ReasoningLevel>,
|
||||
pub shell_type: ShellType,
|
||||
pub default_reasoning_level: ReasoningEffort,
|
||||
pub supported_reasoning_levels: Vec<ReasoningEffort>,
|
||||
pub shell_type: ConfigShellToolType,
|
||||
#[serde(default = "default_visibility")]
|
||||
pub visibility: ModelVisibility,
|
||||
pub minimal_client_version: ClientVersion,
|
||||
@@ -147,3 +143,28 @@ pub struct ModelsResponse {
|
||||
fn default_visibility() -> ModelVisibility {
|
||||
ModelVisibility::None
|
||||
}
|
||||
|
||||
// convert ModelInfo to ModelPreset
|
||||
impl From<ModelInfo> for ModelPreset {
|
||||
fn from(info: ModelInfo) -> Self {
|
||||
ModelPreset {
|
||||
id: info.slug.clone(),
|
||||
model: info.slug,
|
||||
display_name: info.display_name,
|
||||
description: info.description.unwrap_or_default(),
|
||||
default_reasoning_effort: info.default_reasoning_level,
|
||||
supported_reasoning_efforts: info
|
||||
.supported_reasoning_levels
|
||||
.into_iter()
|
||||
.map(|level| ReasoningEffortPreset {
|
||||
effort: level,
|
||||
// todo: add description for each reasoning effort
|
||||
description: level.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
is_default: false, // default is the highest priority available model
|
||||
upgrade: None, // no upgrade available (todo: think about it)
|
||||
show_in_picker: info.visibility == ModelVisibility::List,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ fn make_chatwidget_manual() -> (
|
||||
active_cell: None,
|
||||
config: cfg.clone(),
|
||||
auth_manager: auth_manager.clone(),
|
||||
models_manager: Arc::new(ModelsManager::new(auth_manager.get_auth_mode())),
|
||||
models_manager: Arc::new(ModelsManager::new(auth_manager)),
|
||||
session_header: SessionHeader::new(cfg.model),
|
||||
initial_user_message: None,
|
||||
token_info: None,
|
||||
@@ -431,7 +431,7 @@ fn make_chatwidget_manual() -> (
|
||||
fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
||||
chat.auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.get_auth_mode()));
|
||||
chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone()));
|
||||
}
|
||||
|
||||
pub(crate) fn make_chatwidget_manual_with_sender() -> (
|
||||
|
||||
@@ -1513,6 +1513,8 @@ mod tests {
|
||||
use crate::exec_cell::CommandOutput;
|
||||
use crate::exec_cell::ExecCall;
|
||||
use crate::exec_cell::ExecCell;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::ConfigToml;
|
||||
@@ -1520,7 +1522,6 @@ mod tests {
|
||||
use codex_core::config::types::McpServerTransportConfig;
|
||||
use codex_core::openai_models::models_manager::ModelsManager;
|
||||
use codex_core::protocol::McpAuthStatus;
|
||||
use codex_login::AuthMode;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use dirs::home_dir;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -2325,7 +2326,9 @@ mod tests {
|
||||
#[test]
|
||||
fn reasoning_summary_block() {
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
|
||||
@@ -2342,7 +2345,9 @@ mod tests {
|
||||
#[test]
|
||||
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell =
|
||||
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &model_family);
|
||||
@@ -2357,7 +2362,9 @@ mod tests {
|
||||
config.model = "gpt-3.5-turbo".to_string();
|
||||
config.model_supports_reasoning_summaries = Some(true);
|
||||
config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental);
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
assert_eq!(
|
||||
@@ -2377,7 +2384,9 @@ mod tests {
|
||||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_header_is_missing() {
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing".to_string(),
|
||||
@@ -2391,7 +2400,9 @@ mod tests {
|
||||
#[test]
|
||||
fn reasoning_summary_block_falls_back_when_summary_is_missing() {
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level reasoning without closing**".to_string(),
|
||||
@@ -2413,7 +2424,9 @@ mod tests {
|
||||
#[test]
|
||||
fn reasoning_summary_block_splits_header_and_summary_when_present() {
|
||||
let config = test_config();
|
||||
let models_manager = Arc::new(ModelsManager::new(Some(AuthMode::ApiKey)));
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let models_manager = Arc::new(ModelsManager::new(auth_manager));
|
||||
let model_family = models_manager.construct_model_family(&config.model, &config);
|
||||
let cell = new_reasoning_summary_block(
|
||||
"**High level plan**\n\nWe should fix the bug next.".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user