use bytes::Bytes;
use futures_util::Stream;
use reqwest::Client;
use serde_json::Value;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::transport::{
CallOptions, ClientOptions, HttpTransport, InfraClientError, ServiceEndpoint,
};
const CHAT_COMPLETIONS_PATH: &str = "/v1/model/chat/completions";
const RESPONSES_PATH: &str = "/v1/model/responses";
const MODELS_PATH: &str = "/v1/model/models";
const SERVICE_NAME: &str = "agent-model-infra";
const AUDIENCE: &str = "agent-model-infra";
#[derive(Clone, Debug)]
pub struct ModelClient {
transport: HttpTransport,
}
pub struct ModelStream {
inner: crate::transport::HttpByteStream,
}
impl std::fmt::Debug for ModelStream {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ModelStream(..)")
}
}
impl ModelStream {
pub async fn next_chunk(&mut self) -> Option<Result<Bytes, InfraClientError>> {
futures_util::StreamExt::next(self).await
}
}
impl Stream for ModelStream {
type Item = Result<Bytes, InfraClientError>;
fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(context)
}
}
impl ModelClient {
pub(crate) fn new_with_endpoint(
http: Client,
endpoint: ServiceEndpoint,
options: ClientOptions,
) -> Self {
let endpoint = endpoint.with_default_credential_audience(AUDIENCE);
Self {
transport: HttpTransport::new_with_options(http, SERVICE_NAME, endpoint, options),
}
}
pub async fn chat_completions(
&self,
request: &Value,
options: CallOptions,
) -> Result<Value, InfraClientError> {
self.transport
.post_json_with_options(CHAT_COMPLETIONS_PATH, request, options)
.await
}
pub async fn chat_completions_stream(
&self,
request: &Value,
options: CallOptions,
) -> Result<ModelStream, InfraClientError> {
self.stream(CHAT_COMPLETIONS_PATH, request, options).await
}
pub async fn responses(
&self,
request: &Value,
options: CallOptions,
) -> Result<Value, InfraClientError> {
self.transport
.post_json_with_options(RESPONSES_PATH, request, options)
.await
}
pub async fn responses_stream(
&self,
request: &Value,
options: CallOptions,
) -> Result<ModelStream, InfraClientError> {
self.stream(RESPONSES_PATH, request, options).await
}
pub async fn models(&self) -> Result<Value, InfraClientError> {
self.transport.get_json(MODELS_PATH).await
}
async fn stream(
&self,
path: &str,
request: &Value,
options: CallOptions,
) -> Result<ModelStream, InfraClientError> {
let mut request = request.clone();
let object = request
.as_object_mut()
.ok_or_else(|| InfraClientError::Protocol {
service: SERVICE_NAME,
message: "streaming model request must be a JSON object".into(),
})?;
object.insert("stream".into(), Value::Bool(true));
let inner = self
.transport
.post_json_stream_with_options(path, &request, options)
.await?;
Ok(ModelStream { inner })
}
}