use crate::io::api::{ApiResult, Configuration, Endpoint, Fallback, Param, Params, RemoteResource};
use crate::param;
use crate::prelude::var;
use crate::util::Label;
use bon::Builder;
use color_eyre::eyre::eyre;
use dotenvy;
use serde::{Deserialize, Serialize};
use tracing::debug;
pub type ChatCompletionListResponse = serde_json::Value;
pub type ChatCompletionResponse = serde_json::Value;
pub type ChatCompletionMessagesResponse = serde_json::Value;
pub type CreateResponseOutput = serde_json::Value;
pub type DeleteResponseOutput = serde_json::Value;
pub type ModelDeleteOutput = serde_json::Value;
pub type ResponseInputItemsOutput = serde_json::Value;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Error {
pub code: Option<String>,
pub message: String,
pub param: Option<String>,
#[serde(rename = "type")]
pub error_type: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
pub error: Error,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ListModelsResponse {
pub object: String,
pub data: Vec<Model>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Model {
pub id: String,
pub object: String,
pub created: i64,
pub owned_by: String,
}
#[derive(Builder, Clone, Debug)]
#[builder(start_fn = with_token, on(String, into))]
pub struct Options {
#[builder(start_fn)]
pub token: String,
pub body: Option<String>,
#[builder(default = String::from("api.openai.com"))]
pub domain: String,
pub identifier: Option<String>,
pub after: Option<String>,
pub limit: Option<u32>,
pub order: Option<String>,
pub model: Option<String>,
#[builder(default = vec![])]
pub custom_params: Vec<Param>,
}
impl Configuration for Options {
fn from_env() -> Self {
if let Err(why) = dotenvy::from_filename(".env") {
debug!("=> {} Load .env — {why}", Label::skip());
}
Self {
token: var("OPENAI_API_KEY").unwrap_or_default(),
body: None,
domain: var("OPENAI_SERVER_HOST").unwrap_or_else(|_| String::from("api.openai.com")),
identifier: None,
after: None,
limit: None,
order: None,
model: None,
custom_params: vec![],
}
}
fn with_body(self, value: impl Into<String>) -> Self {
Self {
body: Some(value.into()),
..self
}
}
fn with_domain(self, value: impl Into<String>) -> Self {
Self {
domain: value.into(),
..self
}
}
fn with_identifier(self, value: impl Into<String>) -> Self {
Self {
identifier: Some(value.into()),
..self
}
}
fn token(&self) -> &str {
&self.token
}
fn domain(&self) -> &str {
&self.domain
}
fn identifier(&self) -> Option<&str> {
self.identifier.as_deref()
}
fn with_params(self, params: Vec<Param>) -> Self {
Self {
custom_params: params,
..self
}
}
fn params(&self) -> &[Param] {
&self.custom_params
}
}
impl Options {
pub fn with_after(self, value: impl Into<String>) -> Self {
Self {
after: Some(value.into()),
..self
}
}
pub fn with_limit(self, value: u32) -> Self {
Self { limit: Some(value), ..self }
}
pub fn with_model(self, value: impl Into<String>) -> Self {
Self {
model: Some(value.into()),
..self
}
}
pub fn with_order(self, value: impl Into<String>) -> Self {
Self {
order: Some(value.into()),
..self
}
}
}
pub async fn chat_completion(options: &Options) -> ApiResult<ChatCompletionResponse> {
let template = "openai::api";
let action = "chat-completion";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match &options.body {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(options.token(), None)
.with(param!(Body, value.as_str()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ChatCompletionResponse, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI chat completion request body is required")),
},
| Err(why) => Err(why),
}
}
pub async fn chat_completion_list(options: &Options) -> ApiResult<ChatCompletionListResponse> {
let template = "openai::api";
let action = "chat-completion::list";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => {
let params = Params::new()
.with_auth(options.token(), None)
.with_keyvalue("after", options.after.as_deref().filter(|v| !v.is_empty()))
.with_keyvalue("limit", options.limit.map(|v| v.to_string()).as_deref())
.with_keyvalue("order", options.order.as_deref().filter(|v| !v.is_empty()))
.with_keyvalue("model", options.model.as_deref().filter(|v| !v.is_empty()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ChatCompletionListResponse, Fallback<ErrorResponse>>(response)
}
| Err(why) => Err(why),
}
}
pub async fn chat_completion_delete(options: &Options) -> ApiResult<DeleteResponseOutput> {
let template = "openai::api";
let action = "chat-completion::delete";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<DeleteResponseOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI chat completion identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn chat_completion_messages(options: &Options) -> ApiResult<ChatCompletionMessagesResponse> {
let template = "openai::api";
let action = "chat-completion::messages";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match &options.identifier {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(options.token(), None)
.with_template("identifier", Some(value))
.with_keyvalue("after", options.after.as_deref().filter(|v| !v.is_empty()))
.with_keyvalue("limit", options.limit.map(|v| v.to_string()).as_deref())
.with_keyvalue("order", options.order.as_deref().filter(|v| !v.is_empty()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ChatCompletionMessagesResponse, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI chat completion identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn chat_completion_retrieve(options: &Options) -> ApiResult<ChatCompletionResponse> {
let template = "openai::api";
let action = "chat-completion::retrieve";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ChatCompletionResponse, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI chat completion identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn chat_completion_update(options: &Options) -> ApiResult<ChatCompletionResponse> {
let template = "openai::api";
let action = "chat-completion::update";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match (&options.identifier, &options.body) {
| (Some(id), Some(value)) if !id.is_empty() && !value.is_empty() => {
let params = Params::new()
.with_auth(options.token(), None)
.with_template("identifier", Some(id))
.with(param!(Body, value.as_str()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ChatCompletionResponse, Fallback<ErrorResponse>>(response)
}
| (Some(_), Some(_)) => Err(eyre!("OpenAI chat completion identifier and body are required")),
| _ => Err(eyre!("OpenAI chat completion identifier and body are required")),
},
| Err(why) => Err(why),
}
}
pub async fn model(options: &Options) -> ApiResult<Model> {
let template = "openai::api";
let action = "model";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<Model, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI model identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn model_delete(options: &Options) -> ApiResult<ModelDeleteOutput> {
let template = "openai::api";
let action = "model::delete";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ModelDeleteOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI model identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn models(options: &Options) -> ApiResult<ListModelsResponse> {
let template = "openai::api";
let action = "models";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ListModelsResponse, Fallback<ErrorResponse>>(response)
}
| Err(why) => Err(why),
}
}
pub async fn response(options: &Options) -> ApiResult<CreateResponseOutput> {
let template = "openai::api";
let action = "response";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match &options.body {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(options.token(), None)
.with(param!(Body, value.as_str()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<CreateResponseOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI responses request body is required")),
},
| Err(why) => Err(why),
}
}
pub async fn response_delete(options: &Options) -> ApiResult<DeleteResponseOutput> {
let template = "openai::api";
let action = "response::delete";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<DeleteResponseOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI response identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn response_input_items(options: &Options) -> ApiResult<ResponseInputItemsOutput> {
let template = "openai::api";
let action = "response::input-items";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match &options.identifier {
| Some(value) if !value.is_empty() => {
let params = Params::new()
.with_auth(options.token(), None)
.with_template("identifier", Some(value))
.with_keyvalue("after", options.after.as_deref().filter(|v| !v.is_empty()))
.with_keyvalue("limit", options.limit.map(|v| v.to_string()).as_deref())
.with_keyvalue("order", options.order.as_deref().filter(|v| !v.is_empty()))
.with_custom(options.params())
.build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<ResponseInputItemsOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI response identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn response_cancel(options: &Options) -> ApiResult<CreateResponseOutput> {
let template = "openai::api";
let action = "response::cancel";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<CreateResponseOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI response identifier is required")),
},
| Err(why) => Err(why),
}
}
pub async fn response_retrieve(options: &Options) -> ApiResult<CreateResponseOutput> {
let template = "openai::api";
let action = "response::retrieve";
match Endpoint::from_template(template).map(|e| e.with_domain(options.domain())) {
| Ok(endpoint) => match options.identifier() {
| Some(value) if !value.is_empty() => {
let params = Params::from_config(options).with_custom(options.params()).build();
let response = endpoint.invoke(action, Some(params)).await;
endpoint.handle_or::<CreateResponseOutput, Fallback<ErrorResponse>>(response)
}
| Some(_) | None => Err(eyre!("OpenAI response identifier is required")),
},
| Err(why) => Err(why),
}
}