mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
model-provider: route model discovery through HTTP client factory (#31361)
This commit is contained in:
1 parent
555aa79d5a
commit
5892c7b69d
36 files changed
+658
-131
No files matched your search
@@ -1,3 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -10,11 +13,13 @@ use codex_api::auth_header_telemetry;
|
||||
use codex_api::map_api_error;
|
||||
use codex_feedback::FeedbackRequestTags;
|
||||
use codex_feedback::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_login::AuthEnvTelemetry;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::collect_auth_env_telemetry;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_login::default_client::build_default_reqwest_client_for_route_async;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::manager::ModelsEndpointClient;
|
||||
use codex_models_manager::manager::ModelsEndpointFuture;
|
||||
@@ -38,6 +43,7 @@ const MODELS_ENDPOINT: &str = "/models";
|
||||
pub(crate) struct OpenAiModelsEndpoint {
|
||||
provider_info: ModelProviderInfo,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
transport_builder: Arc<dyn ModelsTransportBuilder>,
|
||||
}
|
||||
|
||||
impl OpenAiModelsEndpoint {
|
||||
@@ -48,6 +54,7 @@ impl OpenAiModelsEndpoint {
|
||||
Self {
|
||||
provider_info,
|
||||
auth_manager,
|
||||
transport_builder: Arc::new(RouteAwareModelsTransportBuilder),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +75,7 @@ impl OpenAiModelsEndpoint {
|
||||
async fn list_models(
|
||||
&self,
|
||||
client_version: &str,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> CoreResult<(Vec<ModelInfo>, Option<String>)> {
|
||||
let _timer =
|
||||
codex_otel::start_global_timer("codex.remote_models.fetch_update.duration_ms", &[]);
|
||||
@@ -75,7 +83,8 @@ impl OpenAiModelsEndpoint {
|
||||
let auth_mode = auth.as_ref().map(CodexAuth::auth_mode);
|
||||
let api_provider = self.provider_info.to_api_provider(auth_mode)?;
|
||||
let api_auth = resolve_provider_auth(auth.as_ref(), &self.provider_info)?;
|
||||
let transport = ReqwestTransport::new(build_reqwest_client());
|
||||
let request_url =
|
||||
ModelsClient::<ReqwestTransport>::request_url(&api_provider, client_version);
|
||||
let auth_telemetry = auth_header_telemetry(api_auth.as_ref());
|
||||
let agent_identity_telemetry = if let Some(CodexAuth::AgentIdentity(auth)) = auth.as_ref() {
|
||||
Some(agent_identity_telemetry(auth))
|
||||
@@ -89,16 +98,20 @@ impl OpenAiModelsEndpoint {
|
||||
agent_identity_telemetry,
|
||||
auth_env: self.auth_env(),
|
||||
});
|
||||
let client = ModelsClient::new(transport, api_provider, api_auth)
|
||||
.with_telemetry(Some(request_telemetry));
|
||||
|
||||
timeout(
|
||||
MODELS_REFRESH_TIMEOUT,
|
||||
client.list_models(client_version, HeaderMap::new()),
|
||||
)
|
||||
timeout(MODELS_REFRESH_TIMEOUT, async {
|
||||
let transport = self
|
||||
.transport_builder
|
||||
.build(http_client_factory, request_url.clone())
|
||||
.await?;
|
||||
let client = ModelsClient::new(transport, api_provider, api_auth)
|
||||
.with_telemetry(Some(request_telemetry));
|
||||
client
|
||||
.list_models(request_url, HeaderMap::new())
|
||||
.await
|
||||
.map_err(map_api_error)
|
||||
})
|
||||
.await
|
||||
.map_err(|_| CodexErr::Timeout)?
|
||||
.map_err(map_api_error)
|
||||
}
|
||||
|
||||
fn auth_env(&self) -> AuthEnvTelemetry {
|
||||
@@ -122,8 +135,48 @@ impl ModelsEndpointClient for OpenAiModelsEndpoint {
|
||||
fn list_models<'a>(
|
||||
&'a self,
|
||||
client_version: &'a str,
|
||||
http_client_factory: HttpClientFactory,
|
||||
) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>> {
|
||||
Box::pin(OpenAiModelsEndpoint::list_models(self, client_version))
|
||||
Box::pin(OpenAiModelsEndpoint::list_models(
|
||||
self,
|
||||
client_version,
|
||||
http_client_factory,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
type ModelsTransportFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = std::io::Result<ReqwestTransport>> + Send + 'a>>;
|
||||
|
||||
/// Builds the concrete transport selected for one models request.
|
||||
///
|
||||
/// Implementations must honor the supplied request-time client factory and exact request URL.
|
||||
trait ModelsTransportBuilder: fmt::Debug + Send + Sync {
|
||||
fn build(
|
||||
&self,
|
||||
http_client_factory: HttpClientFactory,
|
||||
request_url: String,
|
||||
) -> ModelsTransportFuture<'_>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RouteAwareModelsTransportBuilder;
|
||||
|
||||
impl ModelsTransportBuilder for RouteAwareModelsTransportBuilder {
|
||||
fn build(
|
||||
&self,
|
||||
http_client_factory: HttpClientFactory,
|
||||
request_url: String,
|
||||
) -> ModelsTransportFuture<'_> {
|
||||
Box::pin(async move {
|
||||
build_default_reqwest_client_for_route_async(
|
||||
http_client_factory,
|
||||
request_url,
|
||||
ClientRouteClass::Api,
|
||||
)
|
||||
.await
|
||||
.map(ReqwestTransport::new)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +280,42 @@ impl RequestTelemetry for ModelsRequestTelemetry {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU64;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use pretty_assertions::assert_eq;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
use wiremock::matchers::query_param;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RecordingTransportBuilder {
|
||||
observed_request: Arc<Mutex<Option<(OutboundProxyPolicy, String)>>>,
|
||||
}
|
||||
|
||||
impl ModelsTransportBuilder for RecordingTransportBuilder {
|
||||
fn build(
|
||||
&self,
|
||||
http_client_factory: HttpClientFactory,
|
||||
request_url: String,
|
||||
) -> ModelsTransportFuture<'_> {
|
||||
let observed_request = Arc::clone(&self.observed_request);
|
||||
Box::pin(async move {
|
||||
*observed_request
|
||||
.lock()
|
||||
.expect("observed request lock should not be poisoned") =
|
||||
Some((http_client_factory.outbound_proxy_policy(), request_url));
|
||||
Ok(ReqwestTransport::new(build_reqwest_client()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn provider_info_with_command_auth() -> ModelProviderInfo {
|
||||
ModelProviderInfo {
|
||||
@@ -267,4 +353,45 @@ mod tests {
|
||||
|
||||
assert!(!endpoint.has_command_auth());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_request_uses_request_time_proxy_policy_and_exact_url() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/models"))
|
||||
.and(query_param("client_version", "0.0.0"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200).set_body_json(ModelsResponse { models: Vec::new() }),
|
||||
)
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let observed_request = Arc::new(Mutex::new(None));
|
||||
let endpoint = OpenAiModelsEndpoint {
|
||||
provider_info: ModelProviderInfo::create_openai_provider(Some(server.uri())),
|
||||
auth_manager: None,
|
||||
transport_builder: Arc::new(RecordingTransportBuilder {
|
||||
observed_request: Arc::clone(&observed_request),
|
||||
}),
|
||||
};
|
||||
|
||||
endpoint
|
||||
.list_models(
|
||||
"0.0.0",
|
||||
HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy),
|
||||
)
|
||||
.await
|
||||
.expect("models request should succeed");
|
||||
|
||||
assert_eq!(
|
||||
*observed_request
|
||||
.lock()
|
||||
.expect("observed request lock should not be poisoned"),
|
||||
Some((
|
||||
OutboundProxyPolicy::RespectSystemProxy,
|
||||
format!("{}/models?client_version=0.0.0", server.uri()),
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user