feat: add opt-in provider runtime abstraction (#17713)

## Summary

- Add `codex-model-provider` as the runtime home for model-provider
behavior that does not belong in `codex-core`, `codex-login`, or
`codex-api`.
- The new crate wraps configured `ModelProviderInfo` in a
`ModelProvider` trait object that can resolve the API provider config,
provider-scoped auth manager, and request auth provider for each call.
- This centralizes provider auth behavior in one place today, and gives
us an extension point for future provider-specific auth, model listing,
request setup, and related runtime behavior.

## Tests
Ran tests manually to make sure that provider auth under different
configs still work as expected.

---------

Co-authored-by: pakrym-oai <pakrym@openai.com>
This commit is contained in:
Celia Chen
2026-04-16 19:27:45 -07:00
committed by GitHub
parent 91e8eebd03
commit a803790a10
45 changed files with 577 additions and 369 deletions

View File

@@ -96,7 +96,7 @@ pub fn openai_file_uri(file_id: &str) -> String {
pub async fn upload_local_file(
base_url: &str,
auth: &impl AuthProvider,
auth: &dyn AuthProvider,
path: &Path,
) -> Result<UploadedOpenAiFile, OpenAiFileError> {
let metadata = tokio::fs::metadata(path)
@@ -252,7 +252,7 @@ pub async fn upload_local_file(
}
fn authorized_request(
auth: &impl AuthProvider,
auth: &dyn AuthProvider,
method: reqwest::Method,
url: &str,
) -> reqwest::RequestBuilder {
@@ -276,8 +276,8 @@ fn build_reqwest_client() -> reqwest::Client {
#[cfg(test)]
mod tests {
use super::*;
use crate::CoreAuthProvider;
use pretty_assertions::assert_eq;
use reqwest::header::HeaderValue;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
@@ -291,8 +291,21 @@ mod tests {
use wiremock::matchers::method;
use wiremock::matchers::path;
fn chatgpt_auth() -> CoreAuthProvider {
CoreAuthProvider::for_test(Some("token"), Some("account_id"))
#[derive(Clone, Copy)]
struct ChatGptTestAuth;
impl AuthProvider for ChatGptTestAuth {
fn add_auth_headers(&self, headers: &mut reqwest::header::HeaderMap) {
headers.insert(
reqwest::header::AUTHORIZATION,
HeaderValue::from_static("Bearer token"),
);
headers.insert("ChatGPT-Account-ID", HeaderValue::from_static("account_id"));
}
}
fn chatgpt_auth() -> ChatGptTestAuth {
ChatGptTestAuth
}
fn base_url_for(server: &MockServer) -> String {