mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Extract codex-models from core
Move model manager and provider ownership into a new workspace crate and route workspace consumers through codex-models. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Generated
+37
@@ -1433,6 +1433,7 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-file-search",
|
||||
"codex-login",
|
||||
"codex-models",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-rmcp-client",
|
||||
@@ -1478,6 +1479,7 @@ dependencies = [
|
||||
"codex-core",
|
||||
"codex-features",
|
||||
"codex-feedback",
|
||||
"codex-models",
|
||||
"codex-protocol",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
@@ -1854,6 +1856,7 @@ dependencies = [
|
||||
"codex-git",
|
||||
"codex-hooks",
|
||||
"codex-login",
|
||||
"codex-models",
|
||||
"codex-network-proxy",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
@@ -1964,6 +1967,7 @@ dependencies = [
|
||||
"codex-cloud-requirements",
|
||||
"codex-core",
|
||||
"codex-feedback",
|
||||
"codex-models",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -2175,6 +2179,7 @@ name = "codex-lmstudio"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-models",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2229,6 +2234,7 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-core",
|
||||
"codex-features",
|
||||
"codex-models",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
"codex-utils-cli",
|
||||
@@ -2249,6 +2255,31 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-models"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-api",
|
||||
"codex-login",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"http 1.4.0",
|
||||
"maplit",
|
||||
"pretty_assertions",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-network-proxy"
|
||||
version = "0.0.0"
|
||||
@@ -2288,6 +2319,7 @@ dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"codex-core",
|
||||
"codex-models",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
@@ -2579,6 +2611,7 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-file-search",
|
||||
"codex-login",
|
||||
"codex-models",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
@@ -2672,6 +2705,7 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-file-search",
|
||||
"codex-login",
|
||||
"codex-models",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
@@ -2835,6 +2869,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-lmstudio",
|
||||
"codex-models",
|
||||
"codex-ollama",
|
||||
]
|
||||
|
||||
@@ -2877,6 +2912,7 @@ name = "codex-utils-sandbox-summary"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-models",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"pretty_assertions",
|
||||
@@ -3122,6 +3158,7 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-core",
|
||||
"codex-features",
|
||||
"codex-models",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-cargo-bin",
|
||||
|
||||
@@ -34,6 +34,7 @@ members = [
|
||||
"linux-sandbox",
|
||||
"lmstudio",
|
||||
"login",
|
||||
"models",
|
||||
"mcp-server",
|
||||
"network-proxy",
|
||||
"ollama",
|
||||
@@ -119,6 +120,7 @@ codex-keyring-store = { path = "keyring-store" }
|
||||
codex-linux-sandbox = { path = "linux-sandbox" }
|
||||
codex-lmstudio = { path = "lmstudio" }
|
||||
codex-login = { path = "login" }
|
||||
codex-models = { path = "models" }
|
||||
codex-mcp-server = { path = "mcp-server" }
|
||||
codex-network-proxy = { path = "network-proxy" }
|
||||
codex-ollama = { path = "ollama" }
|
||||
|
||||
@@ -18,6 +18,7 @@ codex-arg0 = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -46,9 +46,9 @@ use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -42,6 +42,7 @@ codex-backend-client = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-chatgpt = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
|
||||
@@ -216,7 +216,6 @@ use codex_core::mcp::auth::discover_supported_scopes;
|
||||
use codex_core::mcp::auth::resolve_oauth_scopes;
|
||||
use codex_core::mcp::collect_mcp_snapshot;
|
||||
use codex_core::mcp::group_tools_by_server;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::parse_cursor;
|
||||
use codex_core::plugins::MarketplaceError;
|
||||
use codex_core::plugins::MarketplacePluginSource;
|
||||
@@ -245,6 +244,7 @@ use codex_login::ServerOptions as LoginServerOptions;
|
||||
use codex_login::ShutdownHandle;
|
||||
use codex_login::auth::login_with_chatgpt_auth_tokens;
|
||||
use codex_login::run_login_server;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
|
||||
@@ -58,13 +58,13 @@ use codex_core::default_client::USER_AGENT_SUFFIX;
|
||||
use codex_core::default_client::get_codex_user_agent;
|
||||
use codex_core::default_client::set_default_client_residency_requirement;
|
||||
use codex_core::default_client::set_default_originator;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::auth::ExternalAuthRefreshContext;
|
||||
use codex_login::auth::ExternalAuthRefreshReason;
|
||||
use codex_login::auth::ExternalAuthRefresher;
|
||||
use codex_login::auth::ExternalAuthTokens;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
|
||||
@@ -4,7 +4,7 @@ use codex_app_server_protocol::Model;
|
||||
use codex_app_server_protocol::ModelUpgradeInfo;
|
||||
use codex_app_server_protocol::ReasoningEffortOption;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_core::test_support::all_model_presets;
|
||||
use codex_models::client_version_to_whole;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -84,7 +85,7 @@ pub fn write_models_cache_with_models(
|
||||
let cache_path = codex_home.join("models_cache.json");
|
||||
// DateTime<Utc> serializes to RFC3339 format by default with serde
|
||||
let fetched_at: DateTime<Utc> = Utc::now();
|
||||
let client_version = codex_core::models_manager::client_version_to_whole();
|
||||
let client_version = client_version_to_whole();
|
||||
let cache = json!({
|
||||
"fetched_at": fetched_at,
|
||||
"etag": null,
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
exports_files(
|
||||
[
|
||||
"templates/collaboration_mode/default.md",
|
||||
"templates/collaboration_mode/plan.md",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "model_availability_nux_fixtures",
|
||||
srcs = [
|
||||
"models.json",
|
||||
"tests/cli_responses_fixture.sse",
|
||||
],
|
||||
name = "cli_responses_fixture_sse",
|
||||
srcs = ["tests/cli_responses_fixture.sse"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
@@ -39,7 +28,6 @@ codex_rust_crate(
|
||||
},
|
||||
integration_compile_data_extra = [
|
||||
"//codex-rs/apply-patch:apply_patch_tool_instructions.md",
|
||||
"models.json",
|
||||
"prompt.md",
|
||||
],
|
||||
test_data_extra = [
|
||||
|
||||
@@ -36,6 +36,7 @@ codex-config = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-skills = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
|
||||
@@ -6,16 +6,18 @@ use codex_api::TransportError;
|
||||
use codex_api::error::ApiError;
|
||||
use codex_api::rate_limits::parse_promo_message;
|
||||
use codex_api::rate_limits::parse_rate_limit_for_limit;
|
||||
use codex_models::EnvKeyError;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use http::HeaderMap;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::auth::CodexAuth;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::EnvVarError;
|
||||
use crate::error::RetryLimitReachedError;
|
||||
use crate::error::UnexpectedResponseError;
|
||||
use crate::error::UsageLimitReachedError;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::token_data::PlanType;
|
||||
|
||||
pub(crate) fn map_api_error(err: ApiError) -> CodexErr {
|
||||
@@ -168,7 +170,7 @@ pub(crate) fn auth_provider_from_auth(
|
||||
auth: Option<CodexAuth>,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> crate::error::Result<CoreAuthProvider> {
|
||||
if let Some(api_key) = provider.api_key()? {
|
||||
if let Some(api_key) = provider.api_key().map_err(map_env_key_error)? {
|
||||
return Ok(CoreAuthProvider {
|
||||
token: Some(api_key),
|
||||
account_id: None,
|
||||
@@ -196,6 +198,13 @@ pub(crate) fn auth_provider_from_auth(
|
||||
}
|
||||
}
|
||||
|
||||
fn map_env_key_error(error: EnvKeyError) -> CodexErr {
|
||||
CodexErr::EnvVar(EnvVarError {
|
||||
var: error.var,
|
||||
instructions: error.instructions,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UsageErrorResponse {
|
||||
error: UsageErrorBody,
|
||||
|
||||
@@ -3,7 +3,7 @@ use codex_otel::AuthEnvTelemetryMetadata;
|
||||
use crate::auth::CODEX_API_KEY_ENV_VAR;
|
||||
use crate::auth::OPENAI_API_KEY_ENV_VAR;
|
||||
use crate::auth::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use codex_models::ModelProviderInfo;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct AuthEnvTelemetry {
|
||||
@@ -54,6 +54,7 @@ fn env_var_present(name: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_models::WireApi;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -64,7 +65,7 @@ mod tests {
|
||||
env_key: Some("sk-should-not-leak".to_string()),
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
wire_api: crate::model_provider_info::WireApi::Responses,
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
|
||||
@@ -101,8 +101,6 @@ use crate::default_client::build_reqwest_client;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use crate::response_debug_context::extract_response_debug_context;
|
||||
use crate::response_debug_context::extract_response_debug_context_from_api_error;
|
||||
use crate::response_debug_context::telemetry_api_error_message;
|
||||
@@ -111,6 +109,8 @@ use crate::tools::spec::create_tools_json_for_responses_api;
|
||||
use crate::util::FeedbackRequestTags;
|
||||
use crate::util::emit_feedback_auth_recovery_tags;
|
||||
use crate::util::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
|
||||
pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
|
||||
pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
|
||||
@@ -122,8 +122,7 @@ const RESPONSES_ENDPOINT: &str = "/responses";
|
||||
const RESPONSES_COMPACT_ENDPOINT: &str = "/responses/compact";
|
||||
const MEMORIES_SUMMARIZE_ENDPOINT: &str = "/memories/trace_summarize";
|
||||
#[cfg(test)]
|
||||
pub(crate) const WEBSOCKET_CONNECT_TIMEOUT: Duration =
|
||||
Duration::from_millis(crate::model_provider_info::DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS);
|
||||
pub(crate) const WEBSOCKET_CONNECT_TIMEOUT: Duration = Duration::from_millis(15_000);
|
||||
|
||||
/// Session-scoped state shared by all [`ModelClient`] clones.
|
||||
///
|
||||
@@ -531,7 +530,7 @@ impl ModelClient {
|
||||
let api_provider = self
|
||||
.state
|
||||
.provider
|
||||
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))?;
|
||||
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode));
|
||||
let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?;
|
||||
Ok(CurrentClientSetup {
|
||||
auth,
|
||||
|
||||
@@ -2,6 +2,8 @@ use super::AuthRequestTelemetryContext;
|
||||
use super::ModelClient;
|
||||
use super::PendingUnauthorizedRetry;
|
||||
use super::UnauthorizedRecoveryExecution;
|
||||
use codex_models::WireApi;
|
||||
use codex_models::create_oss_provider_with_base_url;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -11,10 +13,7 @@ use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
fn test_model_client(session_source: SessionSource) -> ModelClient {
|
||||
let provider = crate::model_provider_info::create_oss_provider_with_base_url(
|
||||
"https://example.com/v1",
|
||||
crate::model_provider_info::WireApi::Responses,
|
||||
);
|
||||
let provider = create_oss_provider_with_base_url("https://example.com/v1", WireApi::Responses);
|
||||
ModelClient::new(
|
||||
None,
|
||||
ThreadId::new(),
|
||||
|
||||
+17
-17
@@ -27,10 +27,7 @@ use crate::compact_remote::run_inline_remote_auto_compact_task;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::connectors;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
#[cfg(test)]
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::parse_command::parse_command;
|
||||
use crate::parse_turn_item;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
@@ -65,6 +62,11 @@ use codex_hooks::HookPayload;
|
||||
use codex_hooks::HookResult;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_hooks::HooksConfig;
|
||||
#[cfg(test)]
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::normalize_host;
|
||||
@@ -154,7 +156,6 @@ use tracing::trace_span;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::client::ModelClient;
|
||||
use crate::client::ModelClientSession;
|
||||
use crate::client_common::Prompt;
|
||||
@@ -502,15 +503,10 @@ impl Codex {
|
||||
|
||||
let config = Arc::new(config);
|
||||
let refresh_strategy = match session_source {
|
||||
SessionSource::SubAgent(_) => crate::models_manager::manager::RefreshStrategy::Offline,
|
||||
_ => crate::models_manager::manager::RefreshStrategy::OnlineIfUncached,
|
||||
SessionSource::SubAgent(_) => RefreshStrategy::Offline,
|
||||
_ => RefreshStrategy::OnlineIfUncached,
|
||||
};
|
||||
if config.model.is_none()
|
||||
|| !matches!(
|
||||
refresh_strategy,
|
||||
crate::models_manager::manager::RefreshStrategy::Offline
|
||||
)
|
||||
{
|
||||
if config.model.is_none() || !matches!(refresh_strategy, RefreshStrategy::Offline) {
|
||||
let _ = models_manager.list_models(refresh_strategy).await;
|
||||
}
|
||||
let model = models_manager
|
||||
@@ -521,7 +517,9 @@ impl Codex {
|
||||
// 1. config.base_instructions override
|
||||
// 2. conversation history => session_meta.base_instructions
|
||||
// 3. base_instructions for current model
|
||||
let model_info = models_manager.get_model_info(model.as_str(), &config).await;
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &model_info_config_overrides(&config))
|
||||
.await;
|
||||
let base_instructions = config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -848,7 +846,9 @@ impl TurnContext {
|
||||
pub(crate) async fn with_model(&self, model: String, models_manager: &ModelsManager) -> Self {
|
||||
let mut config = (*self.config).clone();
|
||||
config.model = Some(model.clone());
|
||||
let model_info = models_manager.get_model_info(model.as_str(), &config).await;
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &model_info_config_overrides(&config))
|
||||
.await;
|
||||
let truncation_policy = model_info.truncation_policy.into();
|
||||
let supported_reasoning_levels = model_info
|
||||
.supported_reasoning_levels
|
||||
@@ -2400,7 +2400,7 @@ impl Session {
|
||||
.models_manager
|
||||
.get_model_info(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config,
|
||||
&model_info_config_overrides(&per_turn_config),
|
||||
)
|
||||
.await;
|
||||
let skills_outcome = Arc::new(
|
||||
@@ -5172,7 +5172,7 @@ async fn spawn_review_thread(
|
||||
let review_model_info = sess
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(&model, &config)
|
||||
.get_model_info(&model, &model_info_config_overrides(&config))
|
||||
.await;
|
||||
// For reviews, disable web_search and view_image regardless of global settings.
|
||||
let mut review_features = sess.features.clone();
|
||||
|
||||
@@ -48,7 +48,7 @@ use crate::mcp_tool_call::MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC;
|
||||
use crate::mcp_tool_call::build_guardian_mcp_tool_review_request;
|
||||
use crate::mcp_tool_call::is_mcp_tool_approval_question_id;
|
||||
use crate::mcp_tool_call::lookup_mcp_tool_metadata;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -11,8 +11,9 @@ use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::models_manager::model_info;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::shell::default_user_shell;
|
||||
use crate::test_support::construct_model_info_offline;
|
||||
use crate::tools::format_exec_output_str;
|
||||
|
||||
use codex_features::Features;
|
||||
@@ -62,6 +63,9 @@ use codex_app_server_protocol::AppInfo;
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_execpolicy::NetworkRuleProtocol;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::with_config_overrides;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
@@ -231,9 +235,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
None,
|
||||
ThreadId::try_from("00000000-0000-4000-8000-000000000001")
|
||||
.expect("test thread id should be valid"),
|
||||
crate::model_provider_info::ModelProviderInfo::create_openai_provider(
|
||||
/* base_url */ None,
|
||||
),
|
||||
ModelProviderInfo::create_openai_provider(/* base_url */ None),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
None,
|
||||
false,
|
||||
@@ -504,7 +506,7 @@ async fn get_base_instructions_no_user_content() {
|
||||
let prompt_with_apply_patch_instructions =
|
||||
include_str!("../prompt_with_apply_patch_instructions.md");
|
||||
let models_response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../models/models.json")).expect("valid models.json");
|
||||
let model_info_for_slug = |slug: &str, config: &Config| {
|
||||
let model = models_response
|
||||
.models
|
||||
@@ -512,7 +514,7 @@ async fn get_base_instructions_no_user_content() {
|
||||
.find(|candidate| candidate.slug == slug)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| panic!("model slug {slug} is missing from models.json"));
|
||||
model_info::with_config_overrides(model, config)
|
||||
with_config_overrides(model, &model_info_config_overrides(config))
|
||||
};
|
||||
let test_cases = vec![
|
||||
InstructionsTestCase {
|
||||
@@ -1622,7 +1624,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -1720,7 +1722,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -1870,7 +1872,10 @@ async fn turn_context_with_model_updates_model_fields() {
|
||||
let expected_model_info = session
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info("gpt-5.1", updated.config.as_ref())
|
||||
.get_model_info(
|
||||
"gpt-5.1",
|
||||
&model_info_config_overrides(updated.config.as_ref()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(updated.config.model.as_deref(), Some("gpt-5.1"));
|
||||
@@ -2063,7 +2068,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -2296,7 +2301,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
settings: Settings {
|
||||
@@ -2390,7 +2395,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let exec_policy = Arc::new(ExecPolicyManager::default());
|
||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -2432,7 +2437,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
user_shell_override: None,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
let model_info = construct_model_info_offline(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config,
|
||||
);
|
||||
@@ -3189,7 +3194,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
let exec_policy = Arc::new(ExecPolicyManager::default());
|
||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let model_info = construct_model_info_offline(model.as_str(), config.as_ref());
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -3231,7 +3236,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
user_shell_override: None,
|
||||
};
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
let model_info = construct_model_info_offline(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::Prompt;
|
||||
use crate::client::ModelClientSession;
|
||||
use crate::client_common::ResponseEvent;
|
||||
@@ -19,6 +18,7 @@ use crate::truncate::TruncationPolicy;
|
||||
use crate::truncate::approx_token_count;
|
||||
use crate::truncate::truncate_text;
|
||||
use crate::util::backoff;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::ContentItem;
|
||||
|
||||
@@ -17,6 +17,7 @@ use assert_matches::assert_matches;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_models::WireApi;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -1721,10 +1722,7 @@ fn responses_websocket_features_do_not_change_wire_api() -> std::io::Result<()>
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
config.model_provider.wire_api,
|
||||
crate::model_provider_info::WireApi::Responses
|
||||
);
|
||||
assert_eq!(config.model_provider.wire_api, WireApi::Responses);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -4074,7 +4072,8 @@ fn model_catalog_json_loads_from_path() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let catalog_path = codex_home.path().join("catalog.json");
|
||||
let mut catalog: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
catalog.models = catalog.models.into_iter().take(1).collect();
|
||||
std::fs::write(
|
||||
&catalog_path,
|
||||
@@ -4188,7 +4187,7 @@ model_verbosity = "high"
|
||||
name: "OpenAI custom".to_string(),
|
||||
base_url: Some("https://api.openai.com/v1".to_string()),
|
||||
env_key: Some("OPENAI_API_KEY".to_string()),
|
||||
wire_api: crate::WireApi::Responses,
|
||||
wire_api: WireApi::Responses,
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
query_params: None,
|
||||
|
||||
@@ -41,13 +41,6 @@ use crate::config_loader::Sourced;
|
||||
use crate::config_loader::load_config_layers_state;
|
||||
use crate::git_info::resolve_root_git_project_for_trust;
|
||||
use crate::memories::memory_root;
|
||||
use crate::model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
use crate::model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::OLLAMA_CHAT_PROVIDER_REMOVED_ERROR;
|
||||
use crate::model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use crate::model_provider_info::OPENAI_PROVIDER_ID;
|
||||
use crate::model_provider_info::built_in_model_providers;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
||||
@@ -66,6 +59,13 @@ use codex_features::FeatureConfigSource;
|
||||
use codex_features::FeatureOverrides;
|
||||
use codex_features::Features;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_models::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
use codex_models::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::OLLAMA_CHAT_PROVIDER_REMOVED_ERROR;
|
||||
use codex_models::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_models::OPENAI_PROVIDER_ID;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::Personality;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -274,7 +275,7 @@ pub(super) async fn run_guardian_review_session(
|
||||
let available_models = session
|
||||
.services
|
||||
.models_manager
|
||||
.list_models(crate::models_manager::manager::RefreshStrategy::Offline)
|
||||
.list_models(RefreshStrategy::Offline)
|
||||
.await;
|
||||
let preferred_reasoning_effort = |supports_low: bool, fallback| {
|
||||
if supports_low {
|
||||
|
||||
@@ -34,10 +34,10 @@ use crate::config::ManagedFeatures;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::Permissions;
|
||||
use crate::config::types::McpServerConfig;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::rollout::recorder::RolloutRecorder;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelProviderInfo;
|
||||
|
||||
use super::GUARDIAN_REVIEW_TIMEOUT;
|
||||
use super::GUARDIAN_REVIEWER_NAME;
|
||||
|
||||
@@ -49,7 +49,7 @@ pub mod landlock;
|
||||
pub mod mcp;
|
||||
mod mcp_connection_manager;
|
||||
mod mcp_tool_approval_templates;
|
||||
pub mod models_manager;
|
||||
mod model_info_overrides;
|
||||
mod network_policy_decision;
|
||||
pub mod network_proxy_loader;
|
||||
mod original_image_detail;
|
||||
@@ -63,7 +63,6 @@ mod memories;
|
||||
pub mod mention_syntax;
|
||||
mod mentions;
|
||||
pub mod message_history;
|
||||
mod model_provider_info;
|
||||
pub mod path_utils;
|
||||
pub mod personality_migration;
|
||||
pub mod plugins;
|
||||
@@ -80,15 +79,6 @@ mod truncate;
|
||||
mod unified_exec;
|
||||
pub mod windows_sandbox;
|
||||
pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
|
||||
pub use model_provider_info::DEFAULT_LMSTUDIO_PORT;
|
||||
pub use model_provider_info::DEFAULT_OLLAMA_PORT;
|
||||
pub use model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::ModelProviderInfo;
|
||||
pub use model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::OPENAI_PROVIDER_ID;
|
||||
pub use model_provider_info::WireApi;
|
||||
pub use model_provider_info::built_in_model_providers;
|
||||
pub use model_provider_info::create_oss_provider_with_base_url;
|
||||
mod event_mapping;
|
||||
mod response_debug_context;
|
||||
pub mod review_format;
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::memories::metrics;
|
||||
use crate::memories::phase_one;
|
||||
use crate::memories::phase_one::PRUNE_BATCH_SIZE;
|
||||
use crate::memories::prompts::build_stage_one_input_message;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::rollout::INTERACTIVE_SESSION_SOURCES;
|
||||
use crate::rollout::policy::should_persist_response_item_for_memories;
|
||||
use codex_api::ResponseEvent;
|
||||
@@ -228,7 +229,7 @@ async fn build_request_context(session: &Arc<Session>, config: &Config) -> Reque
|
||||
let model = session
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(&model_name, config)
|
||||
.get_model_info(&model_name, &model_info_config_overrides(config))
|
||||
.await;
|
||||
let turn_context = session.new_default_turn().await;
|
||||
RequestContext::from_turn_context(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::models_manager::model_info::model_info_from_slug;
|
||||
use codex_models::model_info_from_slug;
|
||||
|
||||
#[test]
|
||||
fn build_stage_one_input_message_truncates_rollout_using_model_context_window() {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelInfoConfigOverrides;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub(crate) fn model_info_config_overrides(config: &Config) -> ModelInfoConfigOverrides {
|
||||
ModelInfoConfigOverrides {
|
||||
model_supports_reasoning_summaries: config.model_supports_reasoning_summaries,
|
||||
model_context_window: config.model_context_window,
|
||||
model_auto_compact_token_limit: config.model_auto_compact_token_limit,
|
||||
tool_output_token_limit: config.tool_output_token_limit,
|
||||
base_instructions: config.base_instructions.clone(),
|
||||
personality_enabled: config.features.enabled(Feature::Personality),
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
pub mod cache;
|
||||
pub mod collaboration_mode_presets;
|
||||
pub mod manager;
|
||||
pub mod model_info;
|
||||
pub mod model_presets;
|
||||
|
||||
/// Convert the client version string to a whole version string (e.g. "1.2.3-alpha.4" -> "1.2.3").
|
||||
pub fn client_version_to_whole() -> String {
|
||||
format!(
|
||||
"{}.{}.{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH")
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
use super::*;
|
||||
|
||||
use crate::config::test_config;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use codex_features::Features;
|
||||
use codex_models::ModelsManager;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
"gpt-5-codex",
|
||||
config.model_catalog.as_ref(),
|
||||
&model_info_config_overrides(&config),
|
||||
);
|
||||
model_info.supports_image_detail_original = true;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ImageDetailOriginal);
|
||||
@@ -28,8 +32,11 @@ fn image_detail_original_feature_enables_explicit_original_without_force() {
|
||||
#[test]
|
||||
fn explicit_original_is_dropped_without_feature_or_model_support() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
"gpt-5-codex",
|
||||
config.model_catalog.as_ref(),
|
||||
&model_info_config_overrides(&config),
|
||||
);
|
||||
model_info.supports_image_detail_original = true;
|
||||
let features = Features::with_defaults();
|
||||
|
||||
@@ -50,8 +57,11 @@ fn explicit_original_is_dropped_without_feature_or_model_support() {
|
||||
#[test]
|
||||
fn unsupported_non_original_detail_is_dropped() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
"gpt-5-codex",
|
||||
config.model_catalog.as_ref(),
|
||||
&model_info_config_overrides(&config),
|
||||
);
|
||||
model_info.supports_image_detail_original = true;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ImageDetailOriginal);
|
||||
|
||||
@@ -22,6 +22,8 @@ use codex_api::RealtimeSessionMode;
|
||||
use codex_api::RealtimeWebsocketClient;
|
||||
use codex_api::endpoint::realtime_websocket::RealtimeWebsocketEvents;
|
||||
use codex_api::endpoint::realtime_websocket::RealtimeWebsocketWriter;
|
||||
use codex_models::EnvKeyError;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_protocol::protocol::CodexErrorInfo;
|
||||
use codex_protocol::protocol::ConversationAudioParams;
|
||||
use codex_protocol::protocol::ConversationStartParams;
|
||||
@@ -454,7 +456,7 @@ async fn prepare_realtime_start(
|
||||
let provider = sess.provider().await;
|
||||
let auth = sess.services.auth_manager.auth().await;
|
||||
let realtime_api_key = realtime_api_key(auth.as_ref(), &provider)?;
|
||||
let mut api_provider = provider.to_api_provider(Some(crate::auth::AuthMode::ApiKey))?;
|
||||
let mut api_provider = provider.to_api_provider(Some(crate::auth::AuthMode::ApiKey));
|
||||
let config = sess.get_config().await;
|
||||
if let Some(realtime_ws_base_url) = &config.experimental_realtime_ws_base_url {
|
||||
api_provider.base_url = realtime_ws_base_url.clone();
|
||||
@@ -625,11 +627,8 @@ fn realtime_text_from_handoff_request(handoff: &RealtimeHandoffRequested) -> Opt
|
||||
.or((!handoff.input_transcript.is_empty()).then_some(handoff.input_transcript.clone()))
|
||||
}
|
||||
|
||||
fn realtime_api_key(
|
||||
auth: Option<&CodexAuth>,
|
||||
provider: &crate::ModelProviderInfo,
|
||||
) -> CodexResult<String> {
|
||||
if let Some(api_key) = provider.api_key()? {
|
||||
fn realtime_api_key(auth: Option<&CodexAuth>, provider: &ModelProviderInfo) -> CodexResult<String> {
|
||||
if let Some(api_key) = provider.api_key().map_err(map_env_key_error)? {
|
||||
return Ok(api_key);
|
||||
}
|
||||
|
||||
@@ -654,6 +653,13 @@ fn realtime_api_key(
|
||||
))
|
||||
}
|
||||
|
||||
fn map_env_key_error(error: EnvKeyError) -> CodexErr {
|
||||
CodexErr::EnvVar(crate::error::EnvVarError {
|
||||
var: error.var,
|
||||
instructions: error.instructions,
|
||||
})
|
||||
}
|
||||
|
||||
fn realtime_request_headers(
|
||||
session_id: Option<&str>,
|
||||
api_key: &str,
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::mcp_connection_manager::McpConnectionManager;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills::SkillsManager;
|
||||
use crate::state_db::StateDbHandle;
|
||||
@@ -22,6 +21,7 @@ use crate::tools::sandboxing::ApprovalStore;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use codex_exec_server::Environment;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::hook_runtime::PendingInputHookDisposition;
|
||||
use crate::hook_runtime::inspect_pending_input;
|
||||
use crate::hook_runtime::record_additional_contexts;
|
||||
use crate::hook_runtime::record_pending_input;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::TokenUsage;
|
||||
use crate::protocol::TurnAbortReason;
|
||||
@@ -48,6 +47,7 @@ use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelsManager;
|
||||
pub(crate) use compact::CompactTask;
|
||||
pub(crate) use ghost_snapshot::GhostSnapshotTask;
|
||||
pub(crate) use regular::RegularTask;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::builtin_collaboration_mode_presets as builtin_models_collaboration_mode_presets;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -15,16 +19,14 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use crate::AuthManager;
|
||||
use crate::CodexAuth;
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::ThreadManager;
|
||||
use crate::config::Config;
|
||||
use crate::models_manager::collaboration_mode_presets;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::thread_manager;
|
||||
use crate::unified_exec;
|
||||
|
||||
static TEST_MODEL_PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
|
||||
let file_contents = include_str!("../models.json");
|
||||
let file_contents = include_str!("../../models/models.json");
|
||||
let mut response: ModelsResponse = serde_json::from_str(file_contents)
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
response.models.sort_by(|a, b| a.priority.cmp(&b.priority));
|
||||
@@ -104,7 +106,11 @@ pub fn get_model_offline(model: Option<&str>) -> String {
|
||||
}
|
||||
|
||||
pub fn construct_model_info_offline(model: &str, config: &Config) -> ModelInfo {
|
||||
ModelsManager::construct_model_info_offline_for_tests(model, config)
|
||||
ModelsManager::construct_model_info_offline_for_tests(
|
||||
model,
|
||||
config.model_catalog.as_ref(),
|
||||
&model_info_config_overrides(config),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn all_model_presets() -> &'static Vec<ModelPreset> {
|
||||
@@ -112,7 +118,5 @@ pub fn all_model_presets() -> &'static Vec<ModelPreset> {
|
||||
}
|
||||
|
||||
pub fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
|
||||
collaboration_mode_presets::builtin_collaboration_mode_presets(
|
||||
collaboration_mode_presets::CollaborationModesConfig::default(),
|
||||
)
|
||||
builtin_models_collaboration_mode_presets(CollaborationModesConfig::default())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::AuthManager;
|
||||
use crate::CodexAuth;
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::OPENAI_PROVIDER_ID;
|
||||
use crate::agent::AgentControl;
|
||||
use crate::codex::Codex;
|
||||
use crate::codex::CodexSpawnArgs;
|
||||
@@ -14,8 +12,6 @@ use crate::error::Result as CodexResult;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::file_watcher::FileWatcherEvent;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::protocol::Event;
|
||||
use crate::protocol::EventMsg;
|
||||
@@ -24,6 +20,11 @@ use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::truncation;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::skills::SkillsManager;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::OPENAI_PROVIDER_ID;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -304,10 +305,7 @@ impl ThreadManager {
|
||||
self.state.models_manager.clone()
|
||||
}
|
||||
|
||||
pub async fn list_models(
|
||||
&self,
|
||||
refresh_strategy: crate::models_manager::manager::RefreshStrategy,
|
||||
) -> Vec<ModelPreset> {
|
||||
pub async fn list_models(&self, refresh_strategy: RefreshStrategy) -> Vec<ModelPreset> {
|
||||
self.state
|
||||
.models_manager
|
||||
.list_models(refresh_strategy)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::*;
|
||||
use crate::codex::make_session_and_context;
|
||||
use crate::config::test_config;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use assert_matches::assert_matches;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ReasoningItemReasoningSummary;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::error::CodexErr;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
@@ -22,6 +22,7 @@ use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
use async_trait::async_trait;
|
||||
use codex_features::Feature;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
@@ -340,7 +341,7 @@ async fn apply_requested_spawn_agent_model_overrides(
|
||||
let selected_model_info = session
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(&selected_model_name, config)
|
||||
.get_model_info(&selected_model_name, &model_info_config_overrides(config))
|
||||
.await;
|
||||
|
||||
config.model = Some(selected_model_name.clone());
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::*;
|
||||
use crate::AuthManager;
|
||||
use crate::CodexAuth;
|
||||
use crate::ThreadManager;
|
||||
use crate::built_in_model_providers;
|
||||
use crate::codex::make_session_and_context;
|
||||
use crate::config::DEFAULT_AGENT_MAX_DEPTH;
|
||||
use crate::config::types::ShellEnvironmentPolicy;
|
||||
@@ -17,6 +16,7 @@ use crate::protocol::SubAgentSource;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_features::Feature;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::client_common::tools::ToolSpec;
|
||||
use crate::config::AgentRoleConfig;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::original_image_detail::can_request_original_image_detail;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
@@ -35,6 +34,7 @@ use crate::tools::registry::ToolRegistryBuilder;
|
||||
use crate::tools::registry::tool_handler_key;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_protocol::config_types::WebSearchConfig;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::client_common::tools::FreeformTool;
|
||||
use crate::config::test_config;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::models_manager::model_info::with_config_overrides;
|
||||
use crate::model_info_overrides::model_info_config_overrides;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::registry::ConfiguredToolSpec;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::with_config_overrides;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -54,10 +55,17 @@ fn windows_shell_safety_description() -> String {
|
||||
format!("\n\n{}", super::windows_destructive_filesystem_guidance())
|
||||
}
|
||||
|
||||
fn offline_model_info(config: &crate::config::Config, model: &str) -> ModelInfo {
|
||||
ModelsManager::construct_model_info_offline_for_tests(
|
||||
model,
|
||||
config.model_catalog.as_ref(),
|
||||
&model_info_config_overrides(config),
|
||||
)
|
||||
}
|
||||
|
||||
fn search_capable_model_info() -> ModelInfo {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
model_info.supports_search_tool = true;
|
||||
model_info
|
||||
}
|
||||
@@ -361,13 +369,14 @@ fn strip_descriptions_tool(spec: &mut ToolSpec) {
|
||||
fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
let config = test_config();
|
||||
let response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
let model = response
|
||||
.models
|
||||
.into_iter()
|
||||
.find(|candidate| candidate.slug == slug)
|
||||
.unwrap_or_else(|| panic!("model slug {slug} is missing from models.json"));
|
||||
with_config_overrides(model, &config)
|
||||
with_config_overrides(model, &model_info_config_overrides(&config))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -499,7 +508,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
#[test]
|
||||
fn test_build_specs_collab_tools_enabled() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
let available_models = Vec::new();
|
||||
@@ -523,7 +532,7 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
#[test]
|
||||
fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::SpawnCsv);
|
||||
features.normalize_dependencies();
|
||||
@@ -553,8 +562,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
#[test]
|
||||
fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
model_info.supports_image_detail_original = true;
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -581,8 +589,7 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
#[test]
|
||||
fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
model_info.supports_image_detail_original = true;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ImageDetailOriginal);
|
||||
@@ -620,7 +627,7 @@ fn test_build_specs_artifact_tool_enabled() {
|
||||
let mut config = test_config();
|
||||
let runtime_root = tempfile::TempDir::new().expect("create temp codex home");
|
||||
config.codex_home = runtime_root.path().to_path_buf();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Artifact);
|
||||
let available_models = Vec::new();
|
||||
@@ -640,7 +647,7 @@ fn test_build_specs_artifact_tool_enabled() {
|
||||
#[test]
|
||||
fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::SpawnCsv);
|
||||
features.normalize_dependencies();
|
||||
@@ -676,7 +683,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
#[test]
|
||||
fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -719,7 +726,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
#[test]
|
||||
fn request_permissions_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -757,7 +764,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
#[test]
|
||||
fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::ExecPermissionApprovals);
|
||||
let available_models = Vec::new();
|
||||
@@ -778,7 +785,7 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
#[test]
|
||||
fn get_memory_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.disable(Feature::MemoryTool);
|
||||
let available_models = Vec::new();
|
||||
@@ -801,7 +808,7 @@ fn get_memory_requires_feature_flag() {
|
||||
#[test]
|
||||
fn js_repl_requires_feature_flag() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
|
||||
let available_models = Vec::new();
|
||||
@@ -829,7 +836,7 @@ fn js_repl_requires_feature_flag() {
|
||||
#[test]
|
||||
fn js_repl_enabled_adds_tools() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::JsRepl);
|
||||
|
||||
@@ -850,8 +857,7 @@ fn js_repl_enabled_adds_tools() {
|
||||
#[test]
|
||||
fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let config = test_config();
|
||||
let mut supported_model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5.2", &config);
|
||||
let mut supported_model_info = offline_model_info(&config, "gpt-5.2");
|
||||
supported_model_info.slug = "custom/gpt-5.2-variant".to_string();
|
||||
let mut unsupported_model_info = supported_model_info.clone();
|
||||
unsupported_model_info.input_modalities = vec![InputModality::Text];
|
||||
@@ -984,7 +990,7 @@ fn assert_default_model_tools(
|
||||
#[test]
|
||||
fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
|
||||
let available_models = Vec::new();
|
||||
@@ -1015,7 +1021,7 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
#[test]
|
||||
fn web_search_mode_live_sets_external_web_access_true() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
|
||||
let available_models = Vec::new();
|
||||
@@ -1046,7 +1052,7 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
#[test]
|
||||
fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
let web_search_config = WebSearchConfig {
|
||||
filters: Some(codex_protocol::config_types::WebSearchFilters {
|
||||
@@ -1095,8 +1101,7 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
#[test]
|
||||
fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
let config = test_config();
|
||||
let mut model_info =
|
||||
ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
model_info.web_search_tool_type = WebSearchToolType::TextAndImage;
|
||||
let features = Features::with_defaults();
|
||||
|
||||
@@ -1133,7 +1138,7 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
#[test]
|
||||
fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -1159,7 +1164,7 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
#[test]
|
||||
fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -1398,7 +1403,7 @@ fn test_gpt_5_1_codex_max_unified_exec_web_search() {
|
||||
#[test]
|
||||
fn test_build_specs_default_shell_present() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let model_info = offline_model_info(&config, "o3");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -1424,7 +1429,7 @@ fn test_build_specs_default_shell_present() {
|
||||
#[test]
|
||||
fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let model_info = offline_model_info(&config, "o3");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::ShellZshFork);
|
||||
@@ -1488,7 +1493,7 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
#[ignore]
|
||||
fn test_parallel_support_flags() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -1554,7 +1559,7 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
#[test]
|
||||
fn test_build_specs_mcp_tools_converted() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let model_info = offline_model_info(&config, "o3");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -1647,7 +1652,7 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
#[test]
|
||||
fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("o3", &config);
|
||||
let model_info = offline_model_info(&config, "o3");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -2124,7 +2129,7 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
#[test]
|
||||
fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -2184,7 +2189,7 @@ fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
#[test]
|
||||
fn test_mcp_tool_integer_normalized_to_number() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -2240,7 +2245,7 @@ fn test_mcp_tool_integer_normalized_to_number() {
|
||||
#[test]
|
||||
fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
features.enable(Feature::ApplyPatchFreeform);
|
||||
@@ -2300,7 +2305,7 @@ fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
#[test]
|
||||
fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -2525,7 +2530,7 @@ Examples of valid command strings:
|
||||
#[test]
|
||||
fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
@@ -2635,7 +2640,7 @@ fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
#[test]
|
||||
fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2666,7 +2671,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
#[test]
|
||||
fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::UnifiedExec);
|
||||
@@ -2732,7 +2737,7 @@ fn code_mode_only_restricts_model_tools_to_exec_tools() {
|
||||
#[test]
|
||||
fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::CodeModeOnly);
|
||||
@@ -2765,7 +2770,7 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
#[test]
|
||||
fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only() {
|
||||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let model_info = offline_model_info(&config, "gpt-5-codex");
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
let available_models = Vec::new();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Collaboration Style: Execute
|
||||
You execute on a well-specified task independently and report progress.
|
||||
|
||||
You do not collaborate on decisions in this mode. You execute end-to-end.
|
||||
You make reasonable assumptions when the user hasn't specified something, and you proceed without asking questions.
|
||||
|
||||
## Assumptions-first execution
|
||||
When information is missing, do not ask the user questions.
|
||||
Instead:
|
||||
- Make a sensible assumption.
|
||||
- Clearly state the assumption in the final message (briefly).
|
||||
- Continue executing.
|
||||
|
||||
Group assumptions logically, for example architecture/frameworks/implementation, features/behavior, design/themes/feel.
|
||||
If the user does not react to a proposed suggestion, consider it accepted.
|
||||
|
||||
## Execution principles
|
||||
*Think out loud.* Share reasoning when it helps the user evaluate tradeoffs. Keep explanations short and grounded in consequences. Avoid design lectures or exhaustive option lists.
|
||||
|
||||
*Use reasonable assumptions.* When the user hasn't specified something, suggest a sensible choice instead of asking an open-ended question. Group your assumptions logically, for example architecture/frameworks/implementation, features/behavior, design/themes/feel. Clearly label suggestions as provisional. Share reasoning when it helps the user evaluate tradeoffs. Keep explanations short and grounded in consequences. They should be easy to accept or override. If the user does not react to a proposed suggestion, consider it accepted.
|
||||
|
||||
Example: "There are a few viable ways to structure this. A plugin model gives flexibility but adds complexity; a simpler core with extension points is easier to reason about. Given what you've said about your team's size, I'd lean towards the latter."
|
||||
Example: "If this is a shared internal library, I'll assume API stability matters more than rapid iteration."
|
||||
|
||||
*Think ahead.* What else might the user need? How will the user test and understand what you did? Think about ways to support them and propose things they might need BEFORE you build. Offer at least one suggestion you came up with by thinking ahead.
|
||||
Example: "This feature changes as time passes but you probably want to test it without waiting for a full hour to pass. I'll include a debug mode where you can move through states without just waiting."
|
||||
|
||||
*Be mindful of time.* The user is right here with you. Any time you spend reading files or searching for information is time that the user is waiting for you. Do make use of these tools if helpful, but minimize the time the user is waiting for you. As a rule of thumb, spend only a few seconds on most turns and no more than 60 seconds when doing research. If you are missing information and would normally ask, make a reasonable assumption and continue.
|
||||
Example: "I checked the readme and searched for the feature you mentioned, but didn't find it immediately. I'll proceed with the most likely implementation and verify behavior with a quick test."
|
||||
|
||||
## Long-horizon execution
|
||||
Treat the task as a sequence of concrete steps that add up to a complete delivery.
|
||||
- Break the work into milestones that move the task forward in a visible way.
|
||||
- Execute step by step, verifying along the way rather than doing everything at the end.
|
||||
- If the task is large, keep a running checklist of what is done, what is next, and what is blocked.
|
||||
- Avoid blocking on uncertainty: choose a reasonable default and continue.
|
||||
|
||||
## Reporting progress
|
||||
In this phase you show progress on your task and appraise the user of your progress using plan tool.
|
||||
- Provide updates that directly map to the work you are doing (what changed, what you verified, what remains).
|
||||
- If something fails, report what failed, what you tried, and what you will do next.
|
||||
- When you finish, summarize what you delivered and how the user can validate it.
|
||||
|
||||
## Executing
|
||||
Once you start working, you should execute independently. Your job is to deliver the task and report progress.
|
||||
@@ -1,7 +0,0 @@
|
||||
# Collaboration Style: Pair Programming
|
||||
|
||||
## Build together as you go
|
||||
You treat collaboration as pairing by default. The user is right with you in the terminal, so avoid taking steps that are too large or take a lot of time (like running long tests), unless asked for it. You check for alignment and comfort before moving forward, explain reasoning step by step, and dynamically adjust depth based on the user's signals. There is no need to ask multiple rounds of questions—build as you go. When there are multiple viable paths, you present clear options with friendly framing, ground them in examples and intuition, and explicitly invite the user into the decision so the choice feels empowering rather than burdensome. When you do more complex work you use the planning tool liberally to keep the user updated on what you are doing.
|
||||
|
||||
## Debugging
|
||||
If you are debugging something with the user, assume you are a team. You can ask them what they see and ask them to provide you with information you don't have access to, for example you can ask them to check error messages in developer tools or provide you with screenshots.
|
||||
@@ -5,6 +5,6 @@ codex_rust_crate(
|
||||
crate_name = "core_test_support",
|
||||
crate_srcs = glob(["*.rs"]),
|
||||
lib_data_extra = [
|
||||
"//codex-rs/core:model_availability_nux_fixtures",
|
||||
"//codex-rs/models:model_availability_nux_fixtures",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ base64 = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
use anyhow::Context as _;
|
||||
use anyhow::ensure;
|
||||
use codex_arg0::Arg0PathEntryGuard;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelInfoConfigOverrides;
|
||||
use codex_utils_cargo_bin::CargoBinError;
|
||||
use ctor::ctor;
|
||||
use std::sync::OnceLock;
|
||||
@@ -112,6 +114,17 @@ pub fn test_tmp_path_buf() -> PathBuf {
|
||||
test_tmp_path().into_path_buf()
|
||||
}
|
||||
|
||||
pub fn model_info_config_overrides(config: &Config) -> ModelInfoConfigOverrides {
|
||||
ModelInfoConfigOverrides {
|
||||
model_supports_reasoning_summaries: config.model_supports_reasoning_summaries,
|
||||
model_context_window: config.model_context_window,
|
||||
model_auto_compact_token_limit: config.model_auto_compact_token_limit,
|
||||
tool_output_token_limit: config.tool_output_token_limit,
|
||||
base_instructions: config.base_instructions.clone(),
|
||||
personality_enabled: config.features.enabled(Feature::Personality),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a DotSlash resource and return the resolved executable/file path.
|
||||
pub fn fetch_dotslash_file(
|
||||
dotslash_file: &std::path::Path,
|
||||
|
||||
@@ -7,14 +7,14 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::shell::Shell;
|
||||
use codex_core::shell::get_shell_by_model_provided_path;
|
||||
use codex_features::Feature;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -316,7 +316,7 @@ fn ensure_test_model_catalog(config: &mut Config) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bundled_models_path = codex_utils_cargo_bin::find_resource!("../../models.json")
|
||||
let bundled_models_path = codex_utils_cargo_bin::find_resource!("../../../models/models.json")
|
||||
.context("bundled models.json")?;
|
||||
let bundled_models_contents =
|
||||
std::fs::read_to_string(&bundled_models_path).with_context(|| {
|
||||
|
||||
@@ -3,10 +3,10 @@ use std::sync::Arc;
|
||||
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::Prompt;
|
||||
use codex_core::ResponseEvent;
|
||||
use codex_core::WireApi;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::NewThread;
|
||||
use codex_core::Prompt;
|
||||
use codex_core::ResponseEvent;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::WireApi;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::default_client::originator;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::ThreadId;
|
||||
@@ -1397,7 +1397,8 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
|
||||
.await;
|
||||
|
||||
let mut model_catalog: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
let model = model_catalog
|
||||
.models
|
||||
.iter_mut()
|
||||
@@ -1509,7 +1510,8 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res
|
||||
.await;
|
||||
|
||||
let mut model_catalog: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
let model = model_catalog
|
||||
.models
|
||||
.iter_mut()
|
||||
|
||||
@@ -4,12 +4,12 @@ use codex_api::WS_REQUEST_HEADER_TRACESTATE_CLIENT_METADATA_KEY;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::ModelClientSession;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::Prompt;
|
||||
use codex_core::ResponseEvent;
|
||||
use codex_core::WireApi;
|
||||
use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_otel::current_span_w3c_trace_context;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#![allow(clippy::expect_used)]
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::compact::SUMMARIZATION_PROMPT;
|
||||
use codex_core::compact::SUMMARY_PREFIX;
|
||||
use codex_core::config::Config;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -103,7 +103,8 @@ fn non_openai_model_provider(server: &MockServer) -> ModelProviderInfo {
|
||||
|
||||
fn model_info_with_context_window(slug: &str, context_window: i64) -> ModelInfo {
|
||||
let models_response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
let mut model_info = models_response
|
||||
.models
|
||||
.into_iter()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::model_info_config_overrides;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -20,7 +21,9 @@ async fn offline_model_info_without_tool_output_override() {
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
|
||||
let model_info = manager.get_model_info("gpt-5.1", &config).await;
|
||||
let model_info = manager
|
||||
.get_model_info("gpt-5.1", &model_info_config_overrides(&config))
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
@@ -43,7 +46,9 @@ async fn offline_model_info_with_tool_output_override() {
|
||||
CollaborationModesConfig::default(),
|
||||
);
|
||||
|
||||
let model_info = manager.get_model_info("gpt-5.1-codex", &config).await;
|
||||
let model_info = manager
|
||||
.get_model_info("gpt-5.1-codex", &model_info_config_overrides(&config))
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::config::types::Personality;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_features::Feature;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
@@ -19,6 +19,7 @@ use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::model_info_config_overrides;
|
||||
use core_test_support::responses::ev_completed_with_tokens;
|
||||
use core_test_support::responses::ev_image_generation_call;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
@@ -949,11 +950,17 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
|
||||
"expected {smaller_model_slug} to be available in remote model list"
|
||||
);
|
||||
let large_model_info = models_manager
|
||||
.get_model_info(large_model_slug, &test.config)
|
||||
.get_model_info(
|
||||
large_model_slug,
|
||||
&model_info_config_overrides(test.config.as_ref()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(large_model_info.context_window, Some(large_context_window));
|
||||
let smaller_model_info = models_manager
|
||||
.get_model_info(smaller_model_slug, &test.config)
|
||||
.get_model_info(
|
||||
smaller_model_slug,
|
||||
&model_info_config_overrides(test.config.as_ref()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
smaller_model_info.context_window,
|
||||
|
||||
@@ -6,7 +6,8 @@ use chrono::DateTime;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_models::client_version_to_whole;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -152,7 +153,7 @@ async fn uses_cache_when_version_matches() -> Result<()> {
|
||||
let cache = ModelsCache {
|
||||
fetched_at: Utc::now(),
|
||||
etag: None,
|
||||
client_version: Some(codex_core::models_manager::client_version_to_whole()),
|
||||
client_version: Some(client_version_to_whole()),
|
||||
models: vec![cached_model],
|
||||
};
|
||||
let cache_path = home.join(CACHE_FILE);
|
||||
@@ -243,7 +244,7 @@ async fn refreshes_when_cache_version_differs() -> Result<()> {
|
||||
let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
builder = builder
|
||||
.with_pre_build_hook(move |home| {
|
||||
let client_version = codex_core::models_manager::client_version_to_whole();
|
||||
let client_version = client_version_to_whole();
|
||||
let cache = ModelsCache {
|
||||
fetched_at: Utc::now(),
|
||||
etag: None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use codex_core::config::types::Personality;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::model_info_config_overrides;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
@@ -139,7 +140,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
|
||||
.model
|
||||
.as_deref()
|
||||
.expect("test config should have a model"),
|
||||
&config,
|
||||
&model_info_config_overrides(config.as_ref()),
|
||||
)
|
||||
.await
|
||||
.base_instructions;
|
||||
|
||||
@@ -5,10 +5,10 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_models::built_in_model_providers;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -26,6 +26,7 @@ use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::model_info_config_overrides;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
@@ -105,7 +106,9 @@ async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<(
|
||||
|
||||
manager.list_models(RefreshStrategy::OnlineIfUncached).await;
|
||||
|
||||
let model_info = manager.get_model_info("gpt-5.3-codex-test", &config).await;
|
||||
let model_info = manager
|
||||
.get_model_info("gpt-5.3-codex-test", &model_info_config_overrides(&config))
|
||||
.await;
|
||||
|
||||
assert_eq!(model_info.slug, "gpt-5.3-codex-test");
|
||||
assert_eq!(model_info.base_instructions, specific.base_instructions);
|
||||
@@ -346,7 +349,10 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
|
||||
assert_eq!(requests[0].url.path(), "/v1/models");
|
||||
|
||||
let model_info = models_manager
|
||||
.get_model_info(REMOTE_MODEL_SLUG, &config)
|
||||
.get_model_info(
|
||||
REMOTE_MODEL_SLUG,
|
||||
&model_info_config_overrides(config.as_ref()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(model_info.shell_type, ConfigShellToolType::UnifiedExec);
|
||||
|
||||
@@ -452,7 +458,9 @@ async fn remote_models_truncation_policy_without_override_preserves_remote() ->
|
||||
let models_manager = test.thread_manager.get_models_manager();
|
||||
wait_for_model_available(&models_manager, slug).await;
|
||||
|
||||
let model_info = models_manager.get_model_info(slug, &test.config).await;
|
||||
let model_info = models_manager
|
||||
.get_model_info(slug, &model_info_config_overrides(test.config.as_ref()))
|
||||
.await;
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::bytes(12_000)
|
||||
@@ -497,7 +505,9 @@ async fn remote_models_truncation_policy_with_tool_output_override() -> Result<(
|
||||
let models_manager = test.thread_manager.get_models_manager();
|
||||
wait_for_model_available(&models_manager, slug).await;
|
||||
|
||||
let model_info = models_manager.get_model_info(slug, &test.config).await;
|
||||
let model_info = models_manager
|
||||
.get_model_info(slug, &model_info_config_overrides(test.config.as_ref()))
|
||||
.await;
|
||||
assert_eq!(
|
||||
model_info.truncation_policy,
|
||||
TruncationPolicyConfig::bytes(200)
|
||||
@@ -624,7 +634,9 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
|
||||
|
||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
let base_model_info = models_manager.get_model_info("gpt-5.1", &config).await;
|
||||
let base_model_info = models_manager
|
||||
.get_model_info("gpt-5.1", &model_info_config_overrides(config.as_ref()))
|
||||
.await;
|
||||
let body = response_mock.single_request().body_json();
|
||||
let instructions = body["instructions"].as_str().unwrap();
|
||||
assert_eq!(instructions, base_model_info.base_instructions);
|
||||
@@ -954,8 +966,9 @@ async fn wait_for_model_available(manager: &Arc<ModelsManager>, slug: &str) -> M
|
||||
}
|
||||
|
||||
fn bundled_model_slug() -> String {
|
||||
let response: ModelsResponse = serde_json::from_str(include_str!("../../models.json"))
|
||||
.expect("bundled models.json should deserialize");
|
||||
let response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("bundled models.json should deserialize");
|
||||
response
|
||||
.models
|
||||
.first()
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::time::UNIX_EPOCH;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::config::types::McpServerConfig;
|
||||
use codex_core::config::types::McpServerTransportConfig;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_models::RefreshStrategy;
|
||||
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
|
||||
@@ -95,7 +95,8 @@ fn configure_apps(config: &mut Config, apps_base_url: &str) {
|
||||
config.model = Some("gpt-5-codex".to_string());
|
||||
|
||||
let mut model_catalog: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
serde_json::from_str(include_str!("../../../models/models.json"))
|
||||
.expect("valid models.json");
|
||||
let model = model_catalog
|
||||
.models
|
||||
.iter_mut()
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_core::CodexAuth;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::WireApi;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Verifies that the agent retries when the SSE stream terminates before
|
||||
//! delivering a `response.completed` event.
|
||||
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::WireApi;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::WireApi;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use codex_models::WireApi;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
@@ -43,7 +44,7 @@ async fn websocket_fallback_switches_to_http_on_upgrade_required_connect() -> Re
|
||||
let base_url = format!("{}/v1", server.uri());
|
||||
move |config| {
|
||||
config.model_provider.base_url = Some(base_url);
|
||||
config.model_provider.wire_api = codex_core::WireApi::Responses;
|
||||
config.model_provider.wire_api = WireApi::Responses;
|
||||
config.model_provider.supports_websockets = true;
|
||||
// If we don't treat 426 specially, the sampling loop would retry the WebSocket
|
||||
// handshake before switching to the HTTP transport.
|
||||
@@ -89,7 +90,7 @@ async fn websocket_fallback_switches_to_http_after_retries_exhausted() -> Result
|
||||
let base_url = format!("{}/v1", server.uri());
|
||||
move |config| {
|
||||
config.model_provider.base_url = Some(base_url);
|
||||
config.model_provider.wire_api = codex_core::WireApi::Responses;
|
||||
config.model_provider.wire_api = WireApi::Responses;
|
||||
config.model_provider.supports_websockets = true;
|
||||
config.model_provider.stream_max_retries = Some(2);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
@@ -134,7 +135,7 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result
|
||||
let base_url = format!("{}/v1", server.uri());
|
||||
move |config| {
|
||||
config.model_provider.base_url = Some(base_url);
|
||||
config.model_provider.wire_api = codex_core::WireApi::Responses;
|
||||
config.model_provider.wire_api = WireApi::Responses;
|
||||
config.model_provider.supports_websockets = true;
|
||||
config.model_provider.stream_max_retries = Some(2);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
@@ -209,7 +210,7 @@ async fn websocket_fallback_is_sticky_across_turns() -> Result<()> {
|
||||
let base_url = format!("{}/v1", server.uri());
|
||||
move |config| {
|
||||
config.model_provider.base_url = Some(base_url);
|
||||
config.model_provider.wire_api = codex_core::WireApi::Responses;
|
||||
config.model_provider.wire_api = WireApi::Responses;
|
||||
config.model_provider.supports_websockets = true;
|
||||
config.model_provider.stream_max_retries = Some(2);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
|
||||
@@ -24,6 +24,7 @@ codex-app-server-protocol = { workspace = true }
|
||||
codex-cloud-requirements = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
|
||||
@@ -43,8 +43,6 @@ use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_cloud_requirements::cloud_requirements_loader;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_core::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_core::auth::AuthConfig;
|
||||
use codex_core::auth::enforce_login_restrictions;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
@@ -60,6 +58,8 @@ use codex_core::config_loader::format_config_error_with_source;
|
||||
use codex_core::format_exec_policy_error_with_source;
|
||||
use codex_core::git_info::get_git_repo_root;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_models::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_models::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_otel::set_parent_from_context;
|
||||
use codex_otel::traceparent_context_from_env;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
|
||||
@@ -11,6 +11,7 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
codex-core = { path = "../core" }
|
||||
codex-models = { workspace = true }
|
||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["rt"] }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use codex_core::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_core::config::Config;
|
||||
use codex_models::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ anyhow = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-utils-cli = { workspace = true }
|
||||
|
||||
@@ -6,8 +6,8 @@ use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::default_client::USER_AGENT_SUFFIX;
|
||||
use codex_core::default_client::get_codex_user_agent;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::Submission;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
filegroup(
|
||||
name = "model_availability_nux_fixtures",
|
||||
srcs = [
|
||||
"models.json",
|
||||
"//codex-rs/core:cli_responses_fixture_sse",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
codex_rust_crate(
|
||||
name = "models",
|
||||
crate_name = "codex_models",
|
||||
compile_data = [
|
||||
"models.json",
|
||||
"templates/collaboration_mode/default.md",
|
||||
"templates/collaboration_mode/plan.md",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "codex-models"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_models"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-api = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
http = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "sync", "time"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
http = { workspace = true }
|
||||
maplit = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
|
||||
toml = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
wiremock = { workspace = true }
|
||||
@@ -0,0 +1,84 @@
|
||||
use codex_api::AuthProvider as ApiAuthProvider;
|
||||
use codex_api::TransportError;
|
||||
use codex_api::error::ApiError;
|
||||
use codex_login::CodexAuth;
|
||||
|
||||
use crate::error::ModelsError;
|
||||
use crate::error::Result;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
|
||||
pub(crate) fn map_api_error(err: ApiError) -> ModelsError {
|
||||
match err {
|
||||
ApiError::ContextWindowExceeded => ModelsError::Api("context window exceeded".to_string()),
|
||||
ApiError::QuotaExceeded => ModelsError::Api("quota exceeded".to_string()),
|
||||
ApiError::UsageNotIncluded => ModelsError::Api("usage not included".to_string()),
|
||||
ApiError::Retryable { message, .. } => ModelsError::Api(message),
|
||||
ApiError::Stream(message) => ModelsError::Api(message),
|
||||
ApiError::ServerOverloaded => ModelsError::Api("server overloaded".to_string()),
|
||||
ApiError::Api { status, .. } => ModelsError::Api(format!("api error {}", status.as_u16())),
|
||||
ApiError::InvalidRequest { message } => ModelsError::Api(message),
|
||||
ApiError::Transport(transport) => ModelsError::Api(match transport {
|
||||
TransportError::Http { status, .. } => format!("http {}", status.as_u16()),
|
||||
TransportError::RetryLimit => "retry limit reached".to_string(),
|
||||
TransportError::Timeout => "timeout".to_string(),
|
||||
TransportError::Network(message) | TransportError::Build(message) => message,
|
||||
}),
|
||||
ApiError::RateLimit(message) => ModelsError::Api(message),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn auth_provider_from_auth(
|
||||
auth: Option<CodexAuth>,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> Result<ModelsAuthProvider> {
|
||||
if let Some(api_key) = provider.api_key()? {
|
||||
return Ok(ModelsAuthProvider {
|
||||
token: Some(api_key),
|
||||
account_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(token) = provider.experimental_bearer_token.clone() {
|
||||
return Ok(ModelsAuthProvider {
|
||||
token: Some(token),
|
||||
account_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(auth) = auth {
|
||||
return Ok(ModelsAuthProvider {
|
||||
token: Some(auth.get_token()?),
|
||||
account_id: auth.get_account_id(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ModelsAuthProvider::default())
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct ModelsAuthProvider {
|
||||
token: Option<String>,
|
||||
account_id: Option<String>,
|
||||
}
|
||||
|
||||
impl ModelsAuthProvider {
|
||||
pub(crate) fn auth_header_attached(&self) -> bool {
|
||||
self.token
|
||||
.as_ref()
|
||||
.is_some_and(|token| http::HeaderValue::from_str(&format!("Bearer {token}")).is_ok())
|
||||
}
|
||||
|
||||
pub(crate) fn auth_header_name(&self) -> Option<&'static str> {
|
||||
self.auth_header_attached().then_some("authorization")
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiAuthProvider for ModelsAuthProvider {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
fn account_id(&self) -> Option<String> {
|
||||
self.account_id.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use codex_login::CODEX_API_KEY_ENV_VAR;
|
||||
use codex_login::OPENAI_API_KEY_ENV_VAR;
|
||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct AuthEnvTelemetry {
|
||||
pub(crate) openai_api_key_env_present: bool,
|
||||
pub(crate) codex_api_key_env_present: bool,
|
||||
pub(crate) codex_api_key_env_enabled: bool,
|
||||
pub(crate) provider_env_key_name: Option<String>,
|
||||
pub(crate) provider_env_key_present: Option<bool>,
|
||||
pub(crate) refresh_token_url_override_present: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn collect_auth_env_telemetry(
|
||||
provider: &ModelProviderInfo,
|
||||
codex_api_key_env_enabled: bool,
|
||||
) -> AuthEnvTelemetry {
|
||||
AuthEnvTelemetry {
|
||||
openai_api_key_env_present: env_var_present(OPENAI_API_KEY_ENV_VAR),
|
||||
codex_api_key_env_present: env_var_present(CODEX_API_KEY_ENV_VAR),
|
||||
codex_api_key_env_enabled,
|
||||
provider_env_key_name: provider.env_key.as_ref().map(|_| "configured".to_string()),
|
||||
provider_env_key_present: provider.env_key.as_deref().map(env_var_present),
|
||||
refresh_token_url_override_present: env_var_present(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR),
|
||||
}
|
||||
}
|
||||
|
||||
fn env_var_present(name: &str) -> bool {
|
||||
match std::env::var(name) {
|
||||
Ok(value) => !value.trim().is_empty(),
|
||||
Err(std::env::VarError::NotUnicode(_)) => true,
|
||||
Err(std::env::VarError::NotPresent) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::WireApi;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn collect_auth_env_telemetry_buckets_provider_env_key_name() {
|
||||
let provider = ModelProviderInfo {
|
||||
name: "Custom".to_string(),
|
||||
base_url: None,
|
||||
env_key: Some("sk-should-not-leak".to_string()),
|
||||
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: None,
|
||||
stream_max_retries: None,
|
||||
stream_idle_timeout_ms: None,
|
||||
websocket_connect_timeout_ms: None,
|
||||
requires_openai_auth: false,
|
||||
supports_websockets: false,
|
||||
};
|
||||
|
||||
let telemetry = collect_auth_env_telemetry(&provider, false);
|
||||
|
||||
assert_eq!(
|
||||
telemetry.provider_env_key_name,
|
||||
Some("configured".to_string())
|
||||
);
|
||||
assert_eq!(telemetry.provider_env_key_present, Some(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::model_provider_info::EnvKeyError;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, ModelsError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum ModelsError {
|
||||
#[error(transparent)]
|
||||
EnvVar(#[from] EnvKeyError),
|
||||
#[error("failed to read auth token: {0}")]
|
||||
Auth(#[from] std::io::Error),
|
||||
#[error("timed out while refreshing remote models")]
|
||||
Timeout,
|
||||
#[error("failed to refresh remote models: {0}")]
|
||||
Api(String),
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use crate::auth_env_telemetry::AuthEnvTelemetry;
|
||||
|
||||
pub(crate) struct FeedbackRequestTags<'a> {
|
||||
pub(crate) endpoint: &'a str,
|
||||
pub(crate) auth_header_attached: bool,
|
||||
pub(crate) auth_header_name: Option<&'a str>,
|
||||
pub(crate) auth_mode: Option<&'a str>,
|
||||
pub(crate) auth_retry_after_unauthorized: Option<bool>,
|
||||
pub(crate) auth_recovery_mode: Option<&'a str>,
|
||||
pub(crate) auth_recovery_phase: Option<&'a str>,
|
||||
pub(crate) auth_connection_reused: Option<bool>,
|
||||
pub(crate) auth_request_id: Option<&'a str>,
|
||||
pub(crate) auth_cf_ray: Option<&'a str>,
|
||||
pub(crate) auth_error: Option<&'a str>,
|
||||
pub(crate) auth_error_code: Option<&'a str>,
|
||||
pub(crate) auth_recovery_followup_success: Option<bool>,
|
||||
pub(crate) auth_recovery_followup_status: Option<u16>,
|
||||
}
|
||||
|
||||
struct FeedbackRequestSnapshot<'a> {
|
||||
endpoint: &'a str,
|
||||
auth_header_attached: bool,
|
||||
auth_header_name: &'a str,
|
||||
auth_mode: &'a str,
|
||||
auth_retry_after_unauthorized: String,
|
||||
auth_recovery_mode: &'a str,
|
||||
auth_recovery_phase: &'a str,
|
||||
auth_connection_reused: String,
|
||||
auth_request_id: &'a str,
|
||||
auth_cf_ray: &'a str,
|
||||
auth_error: &'a str,
|
||||
auth_error_code: &'a str,
|
||||
auth_recovery_followup_success: String,
|
||||
auth_recovery_followup_status: String,
|
||||
}
|
||||
|
||||
impl<'a> FeedbackRequestSnapshot<'a> {
|
||||
fn from_tags(tags: &'a FeedbackRequestTags<'a>) -> Self {
|
||||
Self {
|
||||
endpoint: tags.endpoint,
|
||||
auth_header_attached: tags.auth_header_attached,
|
||||
auth_header_name: tags.auth_header_name.unwrap_or(""),
|
||||
auth_mode: tags.auth_mode.unwrap_or(""),
|
||||
auth_retry_after_unauthorized: tags
|
||||
.auth_retry_after_unauthorized
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
auth_recovery_mode: tags.auth_recovery_mode.unwrap_or(""),
|
||||
auth_recovery_phase: tags.auth_recovery_phase.unwrap_or(""),
|
||||
auth_connection_reused: tags
|
||||
.auth_connection_reused
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
auth_request_id: tags.auth_request_id.unwrap_or(""),
|
||||
auth_cf_ray: tags.auth_cf_ray.unwrap_or(""),
|
||||
auth_error: tags.auth_error.unwrap_or(""),
|
||||
auth_error_code: tags.auth_error_code.unwrap_or(""),
|
||||
auth_recovery_followup_success: tags
|
||||
.auth_recovery_followup_success
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
auth_recovery_followup_status: tags
|
||||
.auth_recovery_followup_status
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn emit_feedback_request_tags_with_auth_env(
|
||||
tags: &FeedbackRequestTags<'_>,
|
||||
auth_env: &AuthEnvTelemetry,
|
||||
) {
|
||||
let snapshot = FeedbackRequestSnapshot::from_tags(tags);
|
||||
tracing::info!(
|
||||
target: "feedback_tags",
|
||||
endpoint = tracing::field::debug(snapshot.endpoint),
|
||||
auth_header_attached = tracing::field::debug(snapshot.auth_header_attached),
|
||||
auth_header_name = tracing::field::debug(snapshot.auth_header_name),
|
||||
auth_mode = tracing::field::debug(snapshot.auth_mode),
|
||||
auth_retry_after_unauthorized =
|
||||
tracing::field::debug(snapshot.auth_retry_after_unauthorized),
|
||||
auth_recovery_mode = tracing::field::debug(snapshot.auth_recovery_mode),
|
||||
auth_recovery_phase = tracing::field::debug(snapshot.auth_recovery_phase),
|
||||
auth_connection_reused = tracing::field::debug(snapshot.auth_connection_reused),
|
||||
auth_request_id = tracing::field::debug(snapshot.auth_request_id),
|
||||
auth_cf_ray = tracing::field::debug(snapshot.auth_cf_ray),
|
||||
auth_error = tracing::field::debug(snapshot.auth_error),
|
||||
auth_error_code = tracing::field::debug(snapshot.auth_error_code),
|
||||
auth_recovery_followup_success =
|
||||
tracing::field::debug(snapshot.auth_recovery_followup_success),
|
||||
auth_recovery_followup_status =
|
||||
tracing::field::debug(snapshot.auth_recovery_followup_status),
|
||||
auth_env_openai_api_key_present =
|
||||
tracing::field::debug(auth_env.openai_api_key_env_present),
|
||||
auth_env_codex_api_key_present =
|
||||
tracing::field::debug(auth_env.codex_api_key_env_present),
|
||||
auth_env_codex_api_key_enabled =
|
||||
tracing::field::debug(auth_env.codex_api_key_env_enabled),
|
||||
auth_env_provider_key_name =
|
||||
tracing::field::debug(auth_env.provider_env_key_name.as_deref().unwrap_or("")),
|
||||
auth_env_provider_key_present = tracing::field::debug(
|
||||
&auth_env
|
||||
.provider_env_key_present
|
||||
.map_or_else(String::new, |value| value.to_string())
|
||||
),
|
||||
auth_env_refresh_token_url_override_present =
|
||||
tracing::field::debug(auth_env.refresh_token_url_override_present),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
mod api_bridge;
|
||||
mod auth_env_telemetry;
|
||||
mod error;
|
||||
mod feedback_tags;
|
||||
mod response_debug_context;
|
||||
|
||||
#[path = "models_manager/cache.rs"]
|
||||
pub mod cache;
|
||||
#[path = "models_manager/collaboration_mode_presets.rs"]
|
||||
pub mod collaboration_mode_presets;
|
||||
#[path = "models_manager/manager.rs"]
|
||||
pub mod manager;
|
||||
#[path = "models_manager/model_info.rs"]
|
||||
pub mod model_info;
|
||||
#[path = "models_manager/model_presets.rs"]
|
||||
pub mod model_presets;
|
||||
pub mod model_provider_info;
|
||||
|
||||
pub use collaboration_mode_presets::CollaborationModesConfig;
|
||||
pub use collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
pub use manager::ModelsManager;
|
||||
pub use manager::RefreshStrategy;
|
||||
pub use model_info::ModelInfoConfigOverrides;
|
||||
pub use model_info::model_info_from_slug;
|
||||
pub use model_info::with_config_overrides;
|
||||
pub use model_provider_info::DEFAULT_LMSTUDIO_PORT;
|
||||
pub use model_provider_info::DEFAULT_OLLAMA_PORT;
|
||||
pub use model_provider_info::EnvKeyError;
|
||||
pub use model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
pub use model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::ModelProviderInfo;
|
||||
pub use model_provider_info::OLLAMA_CHAT_PROVIDER_REMOVED_ERROR;
|
||||
pub use model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::OPENAI_PROVIDER_ID;
|
||||
pub use model_provider_info::WireApi;
|
||||
pub use model_provider_info::built_in_model_providers;
|
||||
pub use model_provider_info::create_oss_provider_with_base_url;
|
||||
|
||||
/// Convert the client version string to a whole version string (e.g. "1.2.3-alpha.4" -> "1.2.3").
|
||||
pub fn client_version_to_whole() -> String {
|
||||
format!(
|
||||
"{}.{}.{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH")
|
||||
)
|
||||
}
|
||||
+21
-19
@@ -5,10 +5,9 @@
|
||||
//! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers`
|
||||
//! key. These override or extend the defaults at runtime.
|
||||
|
||||
use crate::auth::AuthMode;
|
||||
use crate::error::EnvVarError;
|
||||
use codex_api::Provider as ApiProvider;
|
||||
use codex_api::provider::RetryConfig as ApiRetryConfig;
|
||||
use codex_login::AuthMode;
|
||||
use http::HeaderMap;
|
||||
use http::header::HeaderName;
|
||||
use http::header::HeaderValue;
|
||||
@@ -18,6 +17,7 @@ use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
const DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 300_000;
|
||||
const DEFAULT_STREAM_MAX_RETRIES: u64 = 5;
|
||||
@@ -31,8 +31,15 @@ const MAX_REQUEST_MAX_RETRIES: u64 = 100;
|
||||
const OPENAI_PROVIDER_NAME: &str = "OpenAI";
|
||||
pub const OPENAI_PROVIDER_ID: &str = "openai";
|
||||
const CHAT_WIRE_API_REMOVED_ERROR: &str = "`wire_api = \"chat\"` is no longer supported.\nHow to fix: set `wire_api = \"responses\"` in your provider config.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
pub(crate) const LEGACY_OLLAMA_CHAT_PROVIDER_ID: &str = "ollama-chat";
|
||||
pub(crate) const OLLAMA_CHAT_PROVIDER_REMOVED_ERROR: &str = "`ollama-chat` is no longer supported.\nHow to fix: replace `ollama-chat` with `ollama` in `model_provider`, `oss_provider`, or `--local-provider`.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
pub const LEGACY_OLLAMA_CHAT_PROVIDER_ID: &str = "ollama-chat";
|
||||
pub const OLLAMA_CHAT_PROVIDER_REMOVED_ERROR: &str = "`ollama-chat` is no longer supported.\nHow to fix: replace `ollama-chat` with `ollama` in `model_provider`, `oss_provider`, or `--local-provider`.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
|
||||
#[derive(Debug, Clone, Error, PartialEq, Eq)]
|
||||
#[error("environment variable {var} is required but missing or empty")]
|
||||
pub struct EnvKeyError {
|
||||
pub var: String,
|
||||
pub instructions: Option<String>,
|
||||
}
|
||||
|
||||
/// Wire protocol that the provider speaks.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
@@ -130,7 +137,7 @@ pub struct ModelProviderInfo {
|
||||
}
|
||||
|
||||
impl ModelProviderInfo {
|
||||
fn build_header_map(&self) -> crate::error::Result<HeaderMap> {
|
||||
fn build_header_map(&self) -> HeaderMap {
|
||||
let capacity = self.http_headers.as_ref().map_or(0, HashMap::len)
|
||||
+ self.env_http_headers.as_ref().map_or(0, HashMap::len);
|
||||
let mut headers = HeaderMap::with_capacity(capacity);
|
||||
@@ -154,13 +161,10 @@ impl ModelProviderInfo {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(headers)
|
||||
headers
|
||||
}
|
||||
|
||||
pub(crate) fn to_api_provider(
|
||||
&self,
|
||||
auth_mode: Option<AuthMode>,
|
||||
) -> crate::error::Result<ApiProvider> {
|
||||
pub fn to_api_provider(&self, auth_mode: Option<AuthMode>) -> ApiProvider {
|
||||
let default_base_url = if matches!(auth_mode, Some(AuthMode::Chatgpt)) {
|
||||
"https://chatgpt.com/backend-api/codex"
|
||||
} else {
|
||||
@@ -171,7 +175,7 @@ impl ModelProviderInfo {
|
||||
.clone()
|
||||
.unwrap_or_else(|| default_base_url.to_string());
|
||||
|
||||
let headers = self.build_header_map()?;
|
||||
let headers = self.build_header_map();
|
||||
let retry = ApiRetryConfig {
|
||||
max_attempts: self.request_max_retries(),
|
||||
base_delay: Duration::from_millis(200),
|
||||
@@ -180,30 +184,28 @@ impl ModelProviderInfo {
|
||||
retry_transport: true,
|
||||
};
|
||||
|
||||
Ok(ApiProvider {
|
||||
ApiProvider {
|
||||
name: self.name.clone(),
|
||||
base_url,
|
||||
query_params: self.query_params.clone(),
|
||||
headers,
|
||||
retry,
|
||||
stream_idle_timeout: self.stream_idle_timeout(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// If `env_key` is Some, returns the API key for this provider if present
|
||||
/// (and non-empty) in the environment. If `env_key` is required but
|
||||
/// cannot be found, returns an error.
|
||||
pub fn api_key(&self) -> crate::error::Result<Option<String>> {
|
||||
pub fn api_key(&self) -> Result<Option<String>, EnvKeyError> {
|
||||
match &self.env_key {
|
||||
Some(env_key) => {
|
||||
let api_key = std::env::var(env_key)
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.ok_or_else(|| {
|
||||
crate::error::CodexErr::EnvVar(EnvVarError {
|
||||
var: env_key.clone(),
|
||||
instructions: self.env_key_instructions.clone(),
|
||||
})
|
||||
.ok_or_else(|| EnvKeyError {
|
||||
var: env_key.clone(),
|
||||
instructions: self.env_key_instructions.clone(),
|
||||
})?;
|
||||
Ok(Some(api_key))
|
||||
}
|
||||
+1
-1
@@ -21,7 +21,7 @@ pub struct CollaborationModesConfig {
|
||||
pub default_mode_request_user_input: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_collaboration_mode_presets(
|
||||
pub fn builtin_collaboration_mode_presets(
|
||||
collaboration_modes_config: CollaborationModesConfig,
|
||||
) -> Vec<CollaborationModeMask> {
|
||||
vec![plan_preset(), default_preset(collaboration_modes_config)]
|
||||
+38
-30
@@ -1,27 +1,27 @@
|
||||
use super::cache::ModelsCacheManager;
|
||||
use crate::api_bridge::auth_provider_from_auth;
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::auth::AuthManager;
|
||||
use crate::auth::AuthMode;
|
||||
use crate::auth::CodexAuth;
|
||||
use crate::auth_env_telemetry::AuthEnvTelemetry;
|
||||
use crate::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use crate::config::Config;
|
||||
use crate::default_client::build_reqwest_client;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CoreResult;
|
||||
use crate::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use crate::error::ModelsError;
|
||||
use crate::error::Result as ModelsResult;
|
||||
use crate::feedback_tags::FeedbackRequestTags;
|
||||
use crate::feedback_tags::emit_feedback_request_tags_with_auth_env;
|
||||
use crate::model_info;
|
||||
use crate::model_info::ModelInfoConfigOverrides;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use crate::models_manager::model_info;
|
||||
use crate::response_debug_context::extract_response_debug_context;
|
||||
use crate::response_debug_context::telemetry_transport_error_message;
|
||||
use crate::util::FeedbackRequestTags;
|
||||
use crate::util::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_api::ModelsClient;
|
||||
use codex_api::RequestTelemetry;
|
||||
use codex_api::ReqwestTransport;
|
||||
use codex_api::TransportError;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::AuthMode;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -310,10 +310,14 @@ impl ModelsManager {
|
||||
|
||||
// todo(aibrahim): look if we can tighten it to pub(crate)
|
||||
/// Look up model metadata, applying remote overrides and config adjustments.
|
||||
#[instrument(level = "info", skip(self, config), fields(model = model))]
|
||||
pub async fn get_model_info(&self, model: &str, config: &Config) -> ModelInfo {
|
||||
#[instrument(level = "info", skip(self, overrides), fields(model = model))]
|
||||
pub async fn get_model_info(
|
||||
&self,
|
||||
model: &str,
|
||||
overrides: &ModelInfoConfigOverrides,
|
||||
) -> ModelInfo {
|
||||
let remote_models = self.get_remote_models().await;
|
||||
Self::construct_model_info_from_candidates(model, &remote_models, config)
|
||||
Self::construct_model_info_from_candidates(model, &remote_models, overrides)
|
||||
}
|
||||
|
||||
fn find_model_by_longest_prefix(model: &str, candidates: &[ModelInfo]) -> Option<ModelInfo> {
|
||||
@@ -355,7 +359,7 @@ impl ModelsManager {
|
||||
fn construct_model_info_from_candidates(
|
||||
model: &str,
|
||||
candidates: &[ModelInfo],
|
||||
config: &Config,
|
||||
overrides: &ModelInfoConfigOverrides,
|
||||
) -> ModelInfo {
|
||||
// First use the normal longest-prefix match. If that misses, allow a narrowly scoped
|
||||
// retry for namespaced slugs like `custom/gpt-5.3-codex`.
|
||||
@@ -370,13 +374,13 @@ impl ModelsManager {
|
||||
} else {
|
||||
model_info::model_info_from_slug(model)
|
||||
};
|
||||
model_info::with_config_overrides(model_info, config)
|
||||
model_info::with_config_overrides(model_info, overrides)
|
||||
}
|
||||
|
||||
/// Refresh models if the provided ETag differs from the cached ETag.
|
||||
///
|
||||
/// Uses `Online` strategy to fetch latest models when ETags differ.
|
||||
pub(crate) async fn refresh_if_new_etag(&self, etag: String) {
|
||||
pub async fn refresh_if_new_etag(&self, etag: String) {
|
||||
let current_etag = self.get_etag().await;
|
||||
if current_etag.clone().is_some() && current_etag.as_deref() == Some(etag.as_str()) {
|
||||
if let Err(err) = self.cache_manager.renew_cache_ttl().await {
|
||||
@@ -390,7 +394,10 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Refresh available models according to the specified strategy.
|
||||
async fn refresh_available_models(&self, refresh_strategy: RefreshStrategy) -> CoreResult<()> {
|
||||
async fn refresh_available_models(
|
||||
&self,
|
||||
refresh_strategy: RefreshStrategy,
|
||||
) -> ModelsResult<()> {
|
||||
// don't override the custom model catalog if one was provided by the user
|
||||
if matches!(self.catalog_mode, CatalogMode::Custom) {
|
||||
return Ok(());
|
||||
@@ -428,12 +435,12 @@ impl ModelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_and_update_models(&self) -> CoreResult<()> {
|
||||
async fn fetch_and_update_models(&self) -> ModelsResult<()> {
|
||||
let _timer =
|
||||
codex_otel::start_global_timer("codex.remote_models.fetch_update.duration_ms", &[]);
|
||||
let auth = self.auth_manager.auth().await;
|
||||
let auth_mode = auth.as_ref().map(CodexAuth::auth_mode);
|
||||
let api_provider = self.provider.to_api_provider(auth_mode)?;
|
||||
let api_provider = self.provider.to_api_provider(auth_mode);
|
||||
let api_auth = auth_provider_from_auth(auth.clone(), &self.provider)?;
|
||||
let auth_env = collect_auth_env_telemetry(
|
||||
&self.provider,
|
||||
@@ -449,13 +456,13 @@ impl ModelsManager {
|
||||
let client = ModelsClient::new(transport, api_provider, api_auth)
|
||||
.with_telemetry(Some(request_telemetry));
|
||||
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
let client_version = crate::client_version_to_whole();
|
||||
let (models, etag) = timeout(
|
||||
MODELS_REFRESH_TIMEOUT,
|
||||
client.list_models(&client_version, HeaderMap::new()),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CodexErr::Timeout)?
|
||||
.map_err(|_| ModelsError::Timeout)?
|
||||
.map_err(map_api_error)?;
|
||||
|
||||
self.apply_remote_models(models.clone()).await;
|
||||
@@ -496,7 +503,7 @@ impl ModelsManager {
|
||||
async fn try_load_cache(&self) -> bool {
|
||||
let _timer =
|
||||
codex_otel::start_global_timer("codex.remote_models.load_cache.duration_ms", &[]);
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
let client_version = crate::client_version_to_whole();
|
||||
info!(client_version, "models cache: evaluating cache eligibility");
|
||||
let cache = match self.cache_manager.load_fresh(&client_version).await {
|
||||
Some(cache) => cache,
|
||||
@@ -538,7 +545,7 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Construct a manager with a specific provider for testing.
|
||||
pub(crate) fn with_provider_for_tests(
|
||||
pub fn with_provider_for_tests(
|
||||
codex_home: PathBuf,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
provider: ModelProviderInfo,
|
||||
@@ -553,7 +560,7 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Get model identifier without consulting remote state or cache.
|
||||
pub(crate) fn get_model_offline_for_tests(model: Option<&str>) -> String {
|
||||
pub fn get_model_offline_for_tests(model: Option<&str>) -> String {
|
||||
if let Some(model) = model {
|
||||
return model.to_string();
|
||||
}
|
||||
@@ -569,16 +576,17 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Build `ModelInfo` without consulting remote state or cache.
|
||||
pub(crate) fn construct_model_info_offline_for_tests(
|
||||
pub fn construct_model_info_offline_for_tests(
|
||||
model: &str,
|
||||
config: &Config,
|
||||
model_catalog: Option<&ModelsResponse>,
|
||||
overrides: &ModelInfoConfigOverrides,
|
||||
) -> ModelInfo {
|
||||
let candidates: &[ModelInfo] = if let Some(model_catalog) = config.model_catalog.as_ref() {
|
||||
let candidates: &[ModelInfo] = if let Some(model_catalog) = model_catalog {
|
||||
&model_catalog.models
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
Self::construct_model_info_from_candidates(model, candidates, config)
|
||||
Self::construct_model_info_from_candidates(model, candidates, overrides)
|
||||
}
|
||||
}
|
||||
|
||||
+62
-31
@@ -1,13 +1,11 @@
|
||||
use super::*;
|
||||
use crate::CodexAuth;
|
||||
use crate::auth::AuthCredentialsStoreMode;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use base64::Engine as _;
|
||||
use chrono::Utc;
|
||||
use codex_api::TransportError;
|
||||
use codex_login::AuthCredentialsStoreMode;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use core_test_support::responses::mount_models_once;
|
||||
use http::HeaderMap;
|
||||
use http::StatusCode;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -24,7 +22,14 @@ use tracing_subscriber::layer::Context;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use wiremock::Match;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path_regex;
|
||||
|
||||
use crate::WireApi;
|
||||
|
||||
fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo {
|
||||
remote_model_with_visibility(slug, display, priority, "list")
|
||||
@@ -92,6 +97,46 @@ fn provider_for(base_url: String) -> ModelProviderInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ModelsMock {
|
||||
requests: Arc<Mutex<Vec<wiremock::Request>>>,
|
||||
}
|
||||
|
||||
impl ModelsMock {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
requests: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn requests(&self) -> Vec<wiremock::Request> {
|
||||
self.requests.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Match for ModelsMock {
|
||||
fn matches(&self, request: &wiremock::Request) -> bool {
|
||||
self.requests.lock().unwrap().push(request.clone());
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
async fn mount_models_once(server: &MockServer, body: ModelsResponse) -> ModelsMock {
|
||||
let models_mock = ModelsMock::new();
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(".*/models$"))
|
||||
.and(models_mock.clone())
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "application/json")
|
||||
.set_body_json(body),
|
||||
)
|
||||
.up_to_n_times(1)
|
||||
.mount(server)
|
||||
.await;
|
||||
models_mock
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TagCollectorVisitor {
|
||||
tags: BTreeMap<String, String>,
|
||||
@@ -136,11 +181,7 @@ where
|
||||
#[tokio::test]
|
||||
async fn get_model_info_tracks_fallback_usage() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let overrides = ModelInfoConfigOverrides::default();
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -156,12 +197,14 @@ async fn get_model_info_tracks_fallback_usage() {
|
||||
.slug
|
||||
.clone();
|
||||
|
||||
let known = manager.get_model_info(known_slug.as_str(), &config).await;
|
||||
let known = manager
|
||||
.get_model_info(known_slug.as_str(), &overrides)
|
||||
.await;
|
||||
assert!(!known.used_fallback_model_metadata);
|
||||
assert_eq!(known.slug, known_slug);
|
||||
|
||||
let unknown = manager
|
||||
.get_model_info("model-that-does-not-exist", &config)
|
||||
.get_model_info("model-that-does-not-exist", &overrides)
|
||||
.await;
|
||||
assert!(unknown.used_fallback_model_metadata);
|
||||
assert_eq!(unknown.slug, "model-that-does-not-exist");
|
||||
@@ -170,11 +213,7 @@ async fn get_model_info_tracks_fallback_usage() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_uses_custom_catalog() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let overrides = ModelInfoConfigOverrides::default();
|
||||
let mut overlay = remote_model("gpt-overlay", "Overlay", 0);
|
||||
overlay.supports_image_detail_original = true;
|
||||
|
||||
@@ -189,7 +228,7 @@ async fn get_model_info_uses_custom_catalog() {
|
||||
);
|
||||
|
||||
let model_info = manager
|
||||
.get_model_info("gpt-overlay-experiment", &config)
|
||||
.get_model_info("gpt-overlay-experiment", &overrides)
|
||||
.await;
|
||||
|
||||
assert_eq!(model_info.slug, "gpt-overlay-experiment");
|
||||
@@ -203,11 +242,7 @@ async fn get_model_info_uses_custom_catalog() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_matches_namespaced_suffix() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let overrides = ModelInfoConfigOverrides::default();
|
||||
let mut remote = remote_model("gpt-image", "Image", 0);
|
||||
remote.supports_image_detail_original = true;
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
@@ -221,7 +256,7 @@ async fn get_model_info_matches_namespaced_suffix() {
|
||||
);
|
||||
let namespaced_model = "custom/gpt-image".to_string();
|
||||
|
||||
let model_info = manager.get_model_info(&namespaced_model, &config).await;
|
||||
let model_info = manager.get_model_info(&namespaced_model, &overrides).await;
|
||||
|
||||
assert_eq!(model_info.slug, namespaced_model);
|
||||
assert!(model_info.supports_image_detail_original);
|
||||
@@ -231,11 +266,7 @@ async fn get_model_info_matches_namespaced_suffix() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_rejects_multi_segment_namespace_suffix_matching() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let overrides = ModelInfoConfigOverrides::default();
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -252,7 +283,7 @@ async fn get_model_info_rejects_multi_segment_namespace_suffix_matching() {
|
||||
.clone();
|
||||
let namespaced_model = format!("ns1/ns2/{known_slug}");
|
||||
|
||||
let model_info = manager.get_model_info(&namespaced_model, &config).await;
|
||||
let model_info = manager.get_model_info(&namespaced_model, &overrides).await;
|
||||
|
||||
assert_eq!(model_info.slug, namespaced_model);
|
||||
assert!(model_info.used_fallback_model_metadata);
|
||||
@@ -444,7 +475,7 @@ async fn refresh_available_models_refetches_when_version_mismatch() {
|
||||
manager
|
||||
.cache_manager
|
||||
.mutate_cache_for_test(|cache| {
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
let client_version = crate::client_version_to_whole();
|
||||
cache.client_version = Some(format!("{client_version}-mismatch"));
|
||||
})
|
||||
.await
|
||||
+29
-15
@@ -1,4 +1,5 @@
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::models::BASE_INSTRUCTIONS_DEFAULT;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelInstructionsVariables;
|
||||
@@ -8,32 +9,41 @@ use codex_protocol::openai_models::TruncationMode;
|
||||
use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::openai_models::default_input_modalities;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::truncate::approx_bytes_for_tokens;
|
||||
use codex_features::Feature;
|
||||
use tracing::warn;
|
||||
|
||||
pub const BASE_INSTRUCTIONS: &str = include_str!("../../prompt.md");
|
||||
const APPROX_BYTES_PER_TOKEN: usize = 4;
|
||||
const DEFAULT_PERSONALITY_HEADER: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
|
||||
const LOCAL_FRIENDLY_TEMPLATE: &str =
|
||||
"You optimize for team morale and being a supportive teammate as much as code quality.";
|
||||
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
|
||||
pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> ModelInfo {
|
||||
if let Some(supports_reasoning_summaries) = config.model_supports_reasoning_summaries
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ModelInfoConfigOverrides {
|
||||
pub model_supports_reasoning_summaries: Option<bool>,
|
||||
pub model_context_window: Option<i64>,
|
||||
pub model_auto_compact_token_limit: Option<i64>,
|
||||
pub tool_output_token_limit: Option<usize>,
|
||||
pub base_instructions: Option<String>,
|
||||
pub personality_enabled: bool,
|
||||
}
|
||||
|
||||
pub fn with_config_overrides(
|
||||
mut model: ModelInfo,
|
||||
overrides: &ModelInfoConfigOverrides,
|
||||
) -> ModelInfo {
|
||||
if let Some(supports_reasoning_summaries) = overrides.model_supports_reasoning_summaries
|
||||
&& supports_reasoning_summaries
|
||||
{
|
||||
model.supports_reasoning_summaries = true;
|
||||
}
|
||||
if let Some(context_window) = config.model_context_window {
|
||||
if let Some(context_window) = overrides.model_context_window {
|
||||
model.context_window = Some(context_window);
|
||||
}
|
||||
if let Some(auto_compact_token_limit) = config.model_auto_compact_token_limit {
|
||||
if let Some(auto_compact_token_limit) = overrides.model_auto_compact_token_limit {
|
||||
model.auto_compact_token_limit = Some(auto_compact_token_limit);
|
||||
}
|
||||
if let Some(token_limit) = config.tool_output_token_limit {
|
||||
if let Some(token_limit) = overrides.tool_output_token_limit {
|
||||
model.truncation_policy = match model.truncation_policy.mode {
|
||||
TruncationMode::Bytes => {
|
||||
let byte_limit =
|
||||
@@ -47,10 +57,10 @@ pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> Mo
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(base_instructions) = &config.base_instructions {
|
||||
if let Some(base_instructions) = &overrides.base_instructions {
|
||||
model.base_instructions = base_instructions.clone();
|
||||
model.model_messages = None;
|
||||
} else if !config.features.enabled(Feature::Personality) {
|
||||
} else if !overrides.personality_enabled {
|
||||
model.model_messages = None;
|
||||
}
|
||||
|
||||
@@ -58,7 +68,7 @@ pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> Mo
|
||||
}
|
||||
|
||||
/// Build a minimal fallback model descriptor for missing/unknown slugs.
|
||||
pub(crate) fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
pub fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
warn!("Unknown model {slug} is used. This will use fallback model metadata.");
|
||||
ModelInfo {
|
||||
slug: slug.to_string(),
|
||||
@@ -72,7 +82,7 @@ pub(crate) fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
priority: 99,
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: BASE_INSTRUCTIONS.to_string(),
|
||||
base_instructions: BASE_INSTRUCTIONS_DEFAULT.to_string(),
|
||||
model_messages: local_personality_messages_for_slug(slug),
|
||||
supports_reasoning_summaries: false,
|
||||
default_reasoning_summary: ReasoningSummary::Auto,
|
||||
@@ -97,7 +107,7 @@ fn local_personality_messages_for_slug(slug: &str) -> Option<ModelMessages> {
|
||||
match slug {
|
||||
"gpt-5.2-codex" | "exp-codex-personality" => Some(ModelMessages {
|
||||
instructions_template: Some(format!(
|
||||
"{DEFAULT_PERSONALITY_HEADER}\n\n{PERSONALITY_PLACEHOLDER}\n\n{BASE_INSTRUCTIONS}"
|
||||
"{DEFAULT_PERSONALITY_HEADER}\n\n{PERSONALITY_PLACEHOLDER}\n\n{BASE_INSTRUCTIONS_DEFAULT}"
|
||||
)),
|
||||
instructions_variables: Some(ModelInstructionsVariables {
|
||||
personality_default: Some(String::new()),
|
||||
@@ -109,6 +119,10 @@ fn local_personality_messages_for_slug(slug: &str) -> Option<ModelMessages> {
|
||||
}
|
||||
}
|
||||
|
||||
fn approx_bytes_for_tokens(tokens: usize) -> usize {
|
||||
tokens.saturating_mul(APPROX_BYTES_PER_TOKEN)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "model_info_tests.rs"]
|
||||
mod tests;
|
||||
+15
-10
@@ -1,14 +1,15 @@
|
||||
use super::*;
|
||||
use crate::config::test_config;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn reasoning_summaries_override_true_enables_support() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(true);
|
||||
let overrides = ModelInfoConfigOverrides {
|
||||
model_supports_reasoning_summaries: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
let updated = with_config_overrides(model.clone(), &overrides);
|
||||
let mut expected = model;
|
||||
expected.supports_reasoning_summaries = true;
|
||||
|
||||
@@ -19,10 +20,12 @@ fn reasoning_summaries_override_true_enables_support() {
|
||||
fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
let mut model = model_info_from_slug("unknown-model");
|
||||
model.supports_reasoning_summaries = true;
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
let overrides = ModelInfoConfigOverrides {
|
||||
model_supports_reasoning_summaries: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
let updated = with_config_overrides(model.clone(), &overrides);
|
||||
|
||||
assert_eq!(updated, model);
|
||||
}
|
||||
@@ -30,10 +33,12 @@ fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
#[test]
|
||||
fn reasoning_summaries_override_false_is_noop_when_model_is_false() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
let overrides = ModelInfoConfigOverrides {
|
||||
model_supports_reasoning_summaries: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
let updated = with_config_overrides(model.clone(), &overrides);
|
||||
|
||||
assert_eq!(updated, model);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
use base64::Engine;
|
||||
use codex_api::TransportError;
|
||||
|
||||
const REQUEST_ID_HEADER: &str = "x-request-id";
|
||||
const OAI_REQUEST_ID_HEADER: &str = "x-oai-request-id";
|
||||
const CF_RAY_HEADER: &str = "cf-ray";
|
||||
const AUTH_ERROR_HEADER: &str = "x-openai-authorization-error";
|
||||
const X_ERROR_JSON_HEADER: &str = "x-error-json";
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ResponseDebugContext {
|
||||
pub(crate) request_id: Option<String>,
|
||||
pub(crate) cf_ray: Option<String>,
|
||||
pub(crate) auth_error: Option<String>,
|
||||
pub(crate) auth_error_code: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn extract_response_debug_context(transport: &TransportError) -> ResponseDebugContext {
|
||||
let mut context = ResponseDebugContext::default();
|
||||
|
||||
let TransportError::Http {
|
||||
headers, body: _, ..
|
||||
} = transport
|
||||
else {
|
||||
return context;
|
||||
};
|
||||
|
||||
let extract_header = |name: &str| {
|
||||
headers
|
||||
.as_ref()
|
||||
.and_then(|headers| headers.get(name))
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::to_string)
|
||||
};
|
||||
|
||||
context.request_id =
|
||||
extract_header(REQUEST_ID_HEADER).or_else(|| extract_header(OAI_REQUEST_ID_HEADER));
|
||||
context.cf_ray = extract_header(CF_RAY_HEADER);
|
||||
context.auth_error = extract_header(AUTH_ERROR_HEADER);
|
||||
context.auth_error_code = extract_header(X_ERROR_JSON_HEADER).and_then(|encoded| {
|
||||
let decoded = base64::engine::general_purpose::STANDARD
|
||||
.decode(encoded)
|
||||
.ok()?;
|
||||
let parsed = serde_json::from_slice::<serde_json::Value>(&decoded).ok()?;
|
||||
parsed
|
||||
.get("error")
|
||||
.and_then(|error| error.get("code"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::to_string)
|
||||
});
|
||||
|
||||
context
|
||||
}
|
||||
|
||||
pub(crate) fn telemetry_transport_error_message(error: &TransportError) -> String {
|
||||
match error {
|
||||
TransportError::Http { status, .. } => format!("http {}", status.as_u16()),
|
||||
TransportError::RetryLimit => "retry limit reached".to_string(),
|
||||
TransportError::Timeout => "timeout".to_string(),
|
||||
TransportError::Network(err) => err.to_string(),
|
||||
TransportError::Build(err) => err.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_api::TransportError;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use http::StatusCode;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::ResponseDebugContext;
|
||||
use super::extract_response_debug_context;
|
||||
use super::telemetry_transport_error_message;
|
||||
|
||||
#[test]
|
||||
fn extract_response_debug_context_decodes_identity_headers() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-oai-request-id", HeaderValue::from_static("req-auth"));
|
||||
headers.insert("cf-ray", HeaderValue::from_static("ray-auth"));
|
||||
headers.insert(
|
||||
"x-openai-authorization-error",
|
||||
HeaderValue::from_static("missing_authorization_header"),
|
||||
);
|
||||
headers.insert(
|
||||
"x-error-json",
|
||||
HeaderValue::from_static("eyJlcnJvciI6eyJjb2RlIjoidG9rZW5fZXhwaXJlZCJ9fQ=="),
|
||||
);
|
||||
|
||||
let context = extract_response_debug_context(&TransportError::Http {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
url: Some("https://chatgpt.com/backend-api/codex/models".to_string()),
|
||||
headers: Some(headers),
|
||||
body: Some(r#"{"error":{"message":"plain text error"},"status":401}"#.to_string()),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
context,
|
||||
ResponseDebugContext {
|
||||
request_id: Some("req-auth".to_string()),
|
||||
cf_ray: Some("ray-auth".to_string()),
|
||||
auth_error: Some("missing_authorization_header".to_string()),
|
||||
auth_error_code: Some("token_expired".to_string()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telemetry_error_messages_omit_http_bodies() {
|
||||
let transport = TransportError::Http {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
url: Some("https://chatgpt.com/backend-api/codex/responses".to_string()),
|
||||
headers: None,
|
||||
body: Some(r#"{"error":{"message":"secret token leaked"}}"#.to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(telemetry_transport_error_message(&transport), "http 401");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telemetry_error_messages_preserve_non_http_details() {
|
||||
let network = TransportError::Network("dns lookup failed".to_string());
|
||||
let build = TransportError::Build("invalid header value".to_string());
|
||||
|
||||
assert_eq!(
|
||||
telemetry_transport_error_message(&network),
|
||||
"dns lookup failed"
|
||||
);
|
||||
assert_eq!(
|
||||
telemetry_transport_error_message(&build),
|
||||
"invalid header value"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ workspace = true
|
||||
async-stream = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "stream"] }
|
||||
semver = { workspace = true }
|
||||
|
||||
@@ -11,9 +11,11 @@ use crate::pull::PullEvent;
|
||||
use crate::pull::PullProgressReporter;
|
||||
use crate::url::base_url_to_host_root;
|
||||
use crate::url::is_openai_compatible_base_url;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_core::config::Config;
|
||||
use codex_models::ModelProviderInfo;
|
||||
use codex_models::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_models::WireApi;
|
||||
use codex_models::create_oss_provider_with_base_url;
|
||||
|
||||
const OLLAMA_CONNECTION_ERROR: &str = "No running Ollama server detected. Start it with: `ollama serve` (after installing). Install instructions: https://github.com/ollama/ollama?tab=readme-ov-file#ollama";
|
||||
|
||||
@@ -47,8 +49,7 @@ impl OllamaClient {
|
||||
|
||||
#[cfg(test)]
|
||||
async fn try_from_provider_with_base_url(base_url: &str) -> io::Result<Self> {
|
||||
let provider =
|
||||
codex_core::create_oss_provider_with_base_url(base_url, codex_core::WireApi::Responses);
|
||||
let provider = create_oss_provider_with_base_url(base_url, WireApi::Responses);
|
||||
Self::try_from_provider(&provider).await
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ mod pull;
|
||||
mod url;
|
||||
|
||||
pub use client::OllamaClient;
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::config::Config;
|
||||
use codex_models::ModelProviderInfo;
|
||||
pub use pull::CliProgressReporter;
|
||||
pub use pull::PullEvent;
|
||||
pub use pull::PullProgressReporter;
|
||||
|
||||
@@ -12,7 +12,7 @@ codex_rust_crate(
|
||||
],
|
||||
allow_empty = True,
|
||||
),
|
||||
test_data_extra = glob(["src/**/snapshots/**"]) + ["//codex-rs/core:model_availability_nux_fixtures"],
|
||||
test_data_extra = glob(["src/**/snapshots/**"]) + ["//codex-rs/models:model_availability_nux_fixtures"],
|
||||
integration_compile_data_extra = ["src/test_backend.rs"],
|
||||
extra_binaries = [
|
||||
"//codex-rs/cli:codex",
|
||||
|
||||
@@ -41,6 +41,7 @@ codex-features = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-models = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
|
||||
@@ -64,13 +64,13 @@ use codex_core::config::types::ModelAvailabilityNuxConfig;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::ConfigLayerStackOrdering;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
|
||||
use codex_core::models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_features::Feature;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::RefreshStrategy;
|
||||
use codex_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
|
||||
use codex_models::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
@@ -72,7 +72,6 @@ use codex_core::git_info::current_branch_name;
|
||||
use codex_core::git_info::get_git_repo_root;
|
||||
use codex_core::git_info::local_git_branches;
|
||||
use codex_core::mcp::McpManager;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_core::plugins::PluginsManager;
|
||||
use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use codex_core::skills::model::SkillMetadata;
|
||||
@@ -80,6 +79,7 @@ use codex_core::skills::model::SkillMetadata;
|
||||
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Feature;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_otel::RuntimeMetricsSummary;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
@@ -33,11 +33,11 @@ use codex_core::config_loader::ConfigLayerStack;
|
||||
use codex_core::config_loader::ConfigRequirements;
|
||||
use codex_core::config_loader::ConfigRequirementsToml;
|
||||
use codex_core::config_loader::RequirementSource;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_core::skills::model::SkillMetadata;
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Feature;
|
||||
use codex_models::CollaborationModesConfig;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_otel::RuntimeMetricsSummary;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
use codex_models::ModelsManager;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::io;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use codex_core::DEFAULT_LMSTUDIO_PORT;
|
||||
use codex_core::DEFAULT_OLLAMA_PORT;
|
||||
use codex_core::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_core::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_core::config::set_default_oss_provider;
|
||||
use codex_models::DEFAULT_LMSTUDIO_PORT;
|
||||
use codex_models::DEFAULT_OLLAMA_PORT;
|
||||
use codex_models::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_models::OLLAMA_OSS_PROVIDER_ID;
|
||||
use crossterm::event::Event;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::history_cell::with_border_with_inner_width;
|
||||
use crate::version::CODEX_CLI_VERSION;
|
||||
use chrono::DateTime;
|
||||
use chrono::Local;
|
||||
use codex_core::WireApi;
|
||||
use codex_core::config::Config;
|
||||
use codex_models::WireApi;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
|
||||
@@ -19,7 +19,7 @@ async fn resume_startup_does_not_consume_model_availability_nux_count() -> Resul
|
||||
let repo_root = codex_utils_cargo_bin::repo_root()?;
|
||||
let codex_home = tempdir()?;
|
||||
|
||||
let source_catalog_path = codex_utils_cargo_bin::find_resource!("../core/models.json")?;
|
||||
let source_catalog_path = codex_utils_cargo_bin::find_resource!("../models/models.json")?;
|
||||
let source_catalog = std::fs::read_to_string(&source_catalog_path)?;
|
||||
let mut source_catalog: JsonValue = serde_json::from_str(&source_catalog)?;
|
||||
let models = source_catalog
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user