mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
V8
This commit is contained in:
@@ -8,6 +8,14 @@ use crate::stream::ResponseStream;
|
||||
pub trait ApiClient: Sized {
|
||||
type Config;
|
||||
|
||||
async fn new(config: Self::Config) -> Result<Self>;
|
||||
async fn stream(&self, prompt: Prompt) -> Result<ResponseStream>;
|
||||
/// Construct a new client instance from the provided configuration.
|
||||
///
|
||||
/// This is synchronous to avoid forcing callers to `await` when no async
|
||||
/// work is needed during construction. If an implementation needs async
|
||||
/// initialization, prefer doing it inside `stream` or provide an explicit
|
||||
/// async initializer on the concrete type.
|
||||
fn new(config: Self::Config) -> Result<Self>;
|
||||
|
||||
/// Start a streaming request for the given prompt.
|
||||
async fn stream(&self, prompt: &Prompt) -> Result<ResponseStream>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::error::Result;
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use serde::Deserialize;
|
||||
@@ -13,5 +14,5 @@ pub struct AuthContext {
|
||||
#[async_trait]
|
||||
pub trait AuthProvider: Send + Sync {
|
||||
async fn auth_context(&self) -> Option<AuthContext>;
|
||||
async fn refresh_token(&self) -> std::result::Result<Option<String>, String>;
|
||||
async fn refresh_token(&self) -> Result<Option<String>>;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,21 @@ use crate::api::ApiClient;
|
||||
use crate::common::apply_subagent_header;
|
||||
use crate::common::backoff;
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use crate::model_provider::ModelProviderInfo;
|
||||
use crate::prompt::Prompt;
|
||||
use crate::stream::ResponseEvent;
|
||||
use crate::stream::ResponseStream;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Configuration for the Chat Completions client (OpenAI-compatible `/v1/chat/completions`).
|
||||
///
|
||||
/// - `http_client`: Reqwest client used for HTTP requests.
|
||||
/// - `provider`: Provider configuration (base URL, headers, retries, etc.).
|
||||
/// - `model`: Model identifier to use.
|
||||
/// - `otel_event_manager`: Telemetry event manager for request/stream instrumentation.
|
||||
/// - `session_source`: Session metadata, used to set subagent headers when applicable.
|
||||
/// - `aggregation_mode`: How to emit streaming output (raw deltas or aggregated).
|
||||
pub struct ChatCompletionsApiClientConfig {
|
||||
pub http_client: reqwest::Client,
|
||||
pub provider: ModelProviderInfo,
|
||||
@@ -50,14 +57,14 @@ pub struct ChatCompletionsApiClient {
|
||||
impl ApiClient for ChatCompletionsApiClient {
|
||||
type Config = ChatCompletionsApiClientConfig;
|
||||
|
||||
async fn new(config: Self::Config) -> Result<Self> {
|
||||
fn new(config: Self::Config) -> Result<Self> {
|
||||
Ok(Self { config })
|
||||
}
|
||||
|
||||
async fn stream(&self, prompt: Prompt) -> Result<ResponseStream> {
|
||||
Self::validate_prompt(&prompt)?;
|
||||
async fn stream(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
Self::validate_prompt(prompt)?;
|
||||
|
||||
let payload = self.build_payload(&prompt)?;
|
||||
let payload = self.build_payload(prompt)?;
|
||||
let (tx_event, rx_event) = mpsc::channel::<Result<ResponseEvent>>(1600);
|
||||
|
||||
let mut attempt: i64 = 0;
|
||||
|
||||
@@ -26,6 +26,7 @@ pub use crate::model_provider::built_in_model_providers;
|
||||
pub use crate::model_provider::create_oss_provider;
|
||||
pub use crate::model_provider::create_oss_provider_with_base_url;
|
||||
pub use crate::prompt::Prompt;
|
||||
pub use crate::prompt::PromptBuilder;
|
||||
pub use crate::responses::ResponsesApiClient;
|
||||
pub use crate::responses::ResponsesApiClientConfig;
|
||||
pub use crate::responses::stream_from_fixture;
|
||||
|
||||
@@ -323,7 +323,7 @@ pub fn create_oss_provider_with_base_url(url: &str) -> ModelProviderInfo {
|
||||
base_url: Some(url.to_string()),
|
||||
env_key: Some("CODEX_OSS_PROVIDER_API_KEY".to_string()),
|
||||
env_key_instructions: Some(
|
||||
"Set CODEx_OSS_PROVIDER_API_KEY to authenticate with this provider.".to_string(),
|
||||
"Set CODEX_OSS_PROVIDER_API_KEY to authenticate with this provider.".to_string(),
|
||||
),
|
||||
experimental_bearer_token: None,
|
||||
wire_api: WireApi::Chat,
|
||||
|
||||
@@ -43,4 +43,83 @@ impl Prompt {
|
||||
session_source,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start building a `Prompt` with a fluent API.
|
||||
pub fn builder() -> PromptBuilder {
|
||||
PromptBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PromptBuilder {
|
||||
instructions: String,
|
||||
input: Vec<ResponseItem>,
|
||||
tools: Vec<Value>,
|
||||
parallel_tool_calls: bool,
|
||||
output_schema: Option<Value>,
|
||||
reasoning: Option<Reasoning>,
|
||||
text_controls: Option<TextControls>,
|
||||
prompt_cache_key: Option<String>,
|
||||
session_source: Option<SessionSource>,
|
||||
}
|
||||
|
||||
impl PromptBuilder {
|
||||
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
|
||||
self.instructions = instructions.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn input(mut self, input: Vec<ResponseItem>) -> Self {
|
||||
self.input = input;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tools(mut self, tools: Vec<Value>) -> Self {
|
||||
self.tools = tools;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parallel_tool_calls(mut self, enabled: bool) -> Self {
|
||||
self.parallel_tool_calls = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn output_schema(mut self, schema: Option<Value>) -> Self {
|
||||
self.output_schema = schema;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reasoning(mut self, reasoning: Option<Reasoning>) -> Self {
|
||||
self.reasoning = reasoning;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_controls(mut self, text_controls: Option<TextControls>) -> Self {
|
||||
self.text_controls = text_controls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn prompt_cache_key(mut self, key: Option<String>) -> Self {
|
||||
self.prompt_cache_key = key;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn session_source(mut self, session: Option<SessionSource>) -> Self {
|
||||
self.session_source = session;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Prompt {
|
||||
Prompt {
|
||||
instructions: self.instructions,
|
||||
input: self.input,
|
||||
tools: self.tools,
|
||||
parallel_tool_calls: self.parallel_tool_calls,
|
||||
output_schema: self.output_schema,
|
||||
reasoning: self.reasoning,
|
||||
text_controls: self.text_controls,
|
||||
prompt_cache_key: self.prompt_cache_key,
|
||||
session_source: self.session_source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,21 @@ use crate::auth::AuthProvider;
|
||||
use crate::common::apply_subagent_header;
|
||||
use crate::common::backoff;
|
||||
use crate::error::Error;
|
||||
use crate::error::Result;
|
||||
use crate::model_provider::ModelProviderInfo;
|
||||
use crate::prompt::Prompt;
|
||||
use crate::stream::ResponseEvent;
|
||||
use crate::stream::ResponseStream;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Configuration for the OpenAI Responses API client (`/v1/responses`).
|
||||
///
|
||||
/// - `http_client`: Reqwest client used for HTTP requests.
|
||||
/// - `provider`: Provider configuration (base URL, headers, retries, etc.).
|
||||
/// - `model`: Model identifier to use.
|
||||
/// - `conversation_id`: Used to set conversation/session headers and cache keys.
|
||||
/// - `auth_provider`: Optional provider of auth context (e.g., ChatGPT login token).
|
||||
/// - `otel_event_manager`: Telemetry event manager for request/stream instrumentation.
|
||||
pub struct ResponsesApiClientConfig {
|
||||
pub http_client: reqwest::Client,
|
||||
pub provider: ModelProviderInfo,
|
||||
@@ -59,18 +66,18 @@ pub struct ResponsesApiClient {
|
||||
impl ApiClient for ResponsesApiClient {
|
||||
type Config = ResponsesApiClientConfig;
|
||||
|
||||
async fn new(config: Self::Config) -> Result<Self> {
|
||||
fn new(config: Self::Config) -> Result<Self> {
|
||||
Ok(Self { config })
|
||||
}
|
||||
|
||||
async fn stream(&self, prompt: Prompt) -> Result<ResponseStream> {
|
||||
async fn stream(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
if self.config.provider.wire_api != crate::model_provider::WireApi::Responses {
|
||||
return Err(Error::UnsupportedOperation(
|
||||
"ResponsesApiClient requires a Responses provider".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut payload_json = self.build_payload(&prompt)?;
|
||||
let mut payload_json = self.build_payload(prompt)?;
|
||||
|
||||
if self.config.provider.is_azure_responses_endpoint()
|
||||
&& let Some(input_value) = payload_json.get_mut("input")
|
||||
@@ -82,7 +89,7 @@ impl ApiClient for ResponsesApiClient {
|
||||
let max_attempts = self.config.provider.request_max_retries();
|
||||
for attempt in 0..=max_attempts {
|
||||
match self
|
||||
.attempt_stream_responses(attempt, &prompt, &payload_json)
|
||||
.attempt_stream_responses(attempt, prompt, &payload_json)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => return Ok(stream),
|
||||
|
||||
@@ -43,14 +43,14 @@ impl RoutedApiClient {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub async fn stream(&self, prompt: Prompt) -> Result<ResponseStream> {
|
||||
pub async fn stream(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
match self.config.provider.wire_api {
|
||||
WireApi::Responses => self.stream_responses(prompt).await,
|
||||
WireApi::Chat => self.stream_chat(prompt).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_responses(&self, prompt: Prompt) -> Result<ResponseStream> {
|
||||
async fn stream_responses(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
if let Some(path) = &self.config.responses_fixture_path {
|
||||
return stream_from_fixture(
|
||||
path,
|
||||
@@ -68,11 +68,11 @@ impl RoutedApiClient {
|
||||
auth_provider: self.config.auth_provider.clone(),
|
||||
otel_event_manager: self.config.otel_event_manager.clone(),
|
||||
};
|
||||
let client = ResponsesApiClient::new(cfg).await?;
|
||||
let client = ResponsesApiClient::new(cfg)?;
|
||||
client.stream(prompt).await
|
||||
}
|
||||
|
||||
async fn stream_chat(&self, prompt: Prompt) -> Result<ResponseStream> {
|
||||
async fn stream_chat(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
let cfg = ChatCompletionsApiClientConfig {
|
||||
http_client: self.config.http_client.clone(),
|
||||
provider: self.config.provider.clone(),
|
||||
@@ -81,7 +81,20 @@ impl RoutedApiClient {
|
||||
session_source: self.config.session_source.clone(),
|
||||
aggregation_mode: self.config.chat_aggregation_mode,
|
||||
};
|
||||
let client = ChatCompletionsApiClient::new(cfg).await?;
|
||||
let client = ChatCompletionsApiClient::new(cfg)?;
|
||||
client.stream(prompt).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ApiClient for RoutedApiClient {
|
||||
type Config = RoutedApiClientConfig;
|
||||
|
||||
fn new(config: Self::Config) -> Result<Self> {
|
||||
Ok(Self::new(config))
|
||||
}
|
||||
|
||||
async fn stream(&self, prompt: &Prompt) -> Result<ResponseStream> {
|
||||
RoutedApiClient::stream(self, prompt).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ impl ModelClient {
|
||||
.await
|
||||
.map_err(map_api_error)?;
|
||||
|
||||
let api_stream = client.stream(api_prompt).await.map_err(map_api_error)?;
|
||||
let api_stream = client.stream(&api_prompt).await.map_err(map_api_error)?;
|
||||
Ok(wrap_stream(api_stream))
|
||||
}
|
||||
|
||||
@@ -269,11 +269,11 @@ impl AuthProvider for AuthManagerProvider {
|
||||
})
|
||||
}
|
||||
|
||||
async fn refresh_token(&self) -> std::result::Result<Option<String>, String> {
|
||||
async fn refresh_token(&self) -> codex_api_client::Result<Option<String>> {
|
||||
self.manager
|
||||
.refresh_token()
|
||||
.await
|
||||
.map_err(|err| err.to_string())
|
||||
.map_err(|err| codex_api_client::Error::Auth(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user