diff --git a/src/openai/mod.rs b/src/openai/mod.rs index 30a62fc..2e55b7c 100644 --- a/src/openai/mod.rs +++ b/src/openai/mod.rs @@ -1 +1,2 @@ pub mod chat; +pub mod models; diff --git a/src/openai/models.rs b/src/openai/models.rs new file mode 100644 index 0000000..d962768 --- /dev/null +++ b/src/openai/models.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +/// The object type, which is always "model". +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ModelObject { + Model, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Model { + /// The model identifier, which can be referenced in the API endpoints. + pub id: Id, + + /// The Unix timestamp (in seconds) when the model was created. + pub created: i64, + + /// The object type, which is always "model". + pub object: ModelObject, + + /// The organization that owns the model. + pub owned_by: String, + + /// The date when the model will shut down, or null if not announced. + #[serde(skip_serializing_if = "Option::is_none")] + pub shutdown_date: Option, +} + +/// The object type, which is always "list". +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ListObject { + List, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ListModelsResponse { + /// The list of currently available models. + pub data: Vec>, + + /// The object type, which is always "list". + pub object: ListObject, +} diff --git a/src/server.rs b/src/server.rs index 48ca4ba..969248c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -10,6 +10,7 @@ use axum::response::Sse; use axum_extra::routing::RouterExt; use axum_extra::routing::TypedPath; use ecow::EcoString; +use enumset::{EnumSet, EnumSetType}; use headers::Header; use headers::authorization::Bearer; use mitsein::vec1::Vec1; @@ -50,7 +51,7 @@ enum Provider { OpenRouter, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, EnumSetType)] #[serde(rename_all = "lowercase")] enum Shortcut { XS, @@ -372,9 +373,7 @@ async fn create_chat_completion( .tls_certs_only( webpki_root_certs::TLS_SERVER_ROOT_CERTS .iter() - .filter_map(|cert| { - Certificate::from_der(&cert).ok() - }), + .filter_map(|cert| Certificate::from_der(&cert).ok()), ) .build() .expect("TODO"); @@ -451,8 +450,32 @@ async fn create_chat_completion( Err(ChatCompletionError::NoneSucceeded) } +#[derive(Deserialize, TypedPath)] +#[typed_path("/v1/models")] +struct V1ModelsPath; + +#[tracing::instrument] +async fn list_models(_: V1ModelsPath) -> Json> { + let models = EnumSet::all() + .into_iter() + .map(|shortcut| openai::models::Model { + id: shortcut, + created: 0, + object: openai::models::ModelObject::Model, + owned_by: "nobody".into(), + shutdown_date: None, + }); + + Json(openai::models::ListModelsResponse { + data: models.collect(), + object: openai::models::ListObject::List, + }) +} + pub fn create_router() -> Router { - Router::new().typed_post(create_chat_completion) + Router::new() + .typed_post(create_chat_completion) + .typed_get(list_models) } pub fn create_app(state: AppState) -> Router<()> {