From 069bd051f2023c08a904a45676d55e8f0c2e2511 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 27 Sep 2026 01:18:02 -0400 Subject: [PATCH] feat: more --- Cargo.lock | 350 ++++++++ Cargo.toml | 5 +- src/main.rs | 2 +- src/openai.rs | 44 - src/openai/chat/completion.rs | 1492 +++++++++++++++++++++++++++++++++ src/openai/chat/mod.rs | 1 + src/openai/mod.rs | 1 + src/server.rs | 102 ++- 8 files changed, 1941 insertions(+), 56 deletions(-) delete mode 100644 src/openai.rs create mode 100644 src/openai/chat/completion.rs create mode 100644 src/openai/chat/mod.rs create mode 100644 src/openai/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 441b7b7..4d0eeaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,12 +94,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "aws-lc-rs" version = "1.18.1" @@ -360,7 +377,10 @@ dependencies = [ "axum", "axum-extra", "clap", + "enum-map", + "enumset", "headers", + "rand 0.10.3", "reqwest", "schemars", "secrecy", @@ -506,6 +526,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + [[package]] name = "crypto-common" version = "0.1.7" @@ -516,6 +566,40 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + [[package]] name = "data-encoding" version = "2.11.1" @@ -570,6 +654,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + [[package]] name = "encoding_rs" version = "0.8.42" @@ -584,6 +674,49 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "enum-map" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4532960034c15ba2f55e9eee631040ecc473c32e6c4b200828c759840f13a06" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7127272f64b3e098f7d81f350d672081b879bff896d219c42370dc858b1af9ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "enumset" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc5801fd11762e24d1e420d01d2ac518f2a2ca4329d4fbb6639f2412b6204e0" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -792,6 +925,76 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hickory-net" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c480823ed7c2c5d0f09c41020cb6b7c28029ce60ec42dc942158dcf22f8e0a4d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "hickory-proto", + "idna", + "ipnet", + "jni", + "rand 0.10.3", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "prefix-trie", + "rand 0.10.3", + "ring", + "thiserror", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3da5255c95d5a716857d54b5b8f4e8d67c3484d3beaaaae2ce25063b3ba981" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.3", + "resolv-conf", + "smallvec", + "system-configuration", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "http" version = "1.5.0" @@ -983,6 +1186,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1014,11 +1223,27 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + [[package]] name = "ipnet" version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" +dependencies = [ + "serde", +] [[package]] name = "is_terminal_polyfill" @@ -1126,6 +1351,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.34" @@ -1196,6 +1430,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moka" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "multer" version = "3.1.0" @@ -1219,6 +1470,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1234,11 +1491,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -1252,6 +1522,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1270,6 +1563,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + [[package]] name = "potential_utf" version = "0.1.6" @@ -1294,6 +1593,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1452,6 +1762,15 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "ref-cast" version = "1.0.27" @@ -1503,6 +1822,7 @@ dependencies = [ "futures-core", "futures-util", "h2", + "hickory-resolver", "http", "http-body", "http-body-util", @@ -1513,6 +1833,7 @@ dependencies = [ "log", "mime", "mime_guess", + "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -1536,6 +1857,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "ring" version = "0.17.14" @@ -1987,6 +2314,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "thiserror" version = "2.0.21" @@ -2327,6 +2660,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -2471,6 +2815,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi-util" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index 8779b20..2149bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,11 @@ edition = "2024" axum = { version = "0.8.9", features = ["http2", "multipart", "ws"] } axum-extra = { version = "0.12.6", features = ["typed-header", "typed-routing"] } clap = { version = "4.6.7", features = ["derive", "env"] } +enum-map = { version = "3.1.0", features = ["serde"] } +enumset = { version = "1.1.14", features = ["serde"] } headers = "0.4.2" -reqwest = { version = "0.13.5", features = ["brotli", "cookies", "deflate", "form", "gzip", "json", "multipart", "query", "stream", "zstd"] } +rand = "0.10.3" +reqwest = { version = "0.13.5", features = ["brotli", "cookies", "deflate", "form", "gzip", "hickory-dns", "json", "multipart", "query", "socks", "stream", "zstd"] } schemars = "1.2.2" secrecy = { version = "0.10.3", features = ["serde"] } serde = { version = "1.0.229", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index 0982222..ca85cb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ struct AppArgs { port: u16, #[arg(long)] - cloudflare_workers_api_key: Vec, + cloudflare_workers_ai_api_key: Vec, #[arg(long)] google_ai_studio_api_key: Vec, diff --git a/src/openai.rs b/src/openai.rs deleted file mode 100644 index 2474a26..0000000 --- a/src/openai.rs +++ /dev/null @@ -1,44 +0,0 @@ -use schemars::Schema; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Message { - role: String, - content: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ResponseFormatJsonSchema { - pub strict: bool, - pub name: String, - pub schema: Schema, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum ResponseFormat { - JsonSchema { - json_schema: ResponseFormatJsonSchema, - }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChatCompletionsRequest { - pub model: Model, - - pub messages: Vec, - - pub response_format: ResponseFormat, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Choice { - pub finish_reason: String, // I don't care - - pub message: Message, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChatCompletionsResponse { - pub choices: Vec, -} diff --git a/src/openai/chat/completion.rs b/src/openai/chat/completion.rs new file mode 100644 index 0000000..855db17 --- /dev/null +++ b/src/openai/chat/completion.rs @@ -0,0 +1,1492 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +// ============================================================================ +// Request: Create Chat Completion +// ============================================================================ + +/// Request body for creating a model response for the given chat conversation (`POST /chat/completions`). +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CreateChatCompletionRequest { + /// A list of messages comprising the conversation so far. Depending on the model you use, + /// different message types (modalities) are supported, like text, images, and audio. + pub messages: Vec, + + /// Model ID used to generate the response, like `gpt-6-astra` or `o3`. + pub model: Model, + + /// Parameters for audio output. Required when audio output is requested with `modalities: ["audio"]`. + #[serde(skip_serializing_if = "Option::is_none")] + pub audio: Option, + + /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing + /// frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. + #[serde(skip_serializing_if = "Option::is_none")] + pub frequency_penalty: Option, + + /// Deprecated in favor of `tool_choice`. Controls which (if any) function is called by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub function_call: Option, + + /// Deprecated in favor of `tools`. A list of functions the model may generate JSON inputs for. + #[serde(skip_serializing_if = "Option::is_none")] + pub functions: Option>, + + /// Modify the likelihood of specified tokens appearing in the completion. Accepts a JSON object + /// that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. + #[serde(skip_serializing_if = "Option::is_none")] + pub logit_bias: Option>, + + /// Whether to return log probabilities of the output tokens or not. If true, returns the log + /// probabilities of each output token returned in the `content` of `message`. + #[serde(skip_serializing_if = "Option::is_none")] + pub logprobs: Option, + + /// An upper bound for the number of tokens that can be generated for a completion, + /// including visible output tokens and reasoning tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_completion_tokens: Option, + + /// The maximum number of tokens that can be generated in the chat completion. + /// Deprecated in favor of `max_completion_tokens`, and is not compatible with o-series models. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_tokens: Option, + + /// Set of 16 key-value pairs that can be attached to an object. + /// Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + + /// Output types that you would like the model to generate. Most models default to `["text"]`. + #[serde(skip_serializing_if = "Option::is_none")] + pub modalities: Option>, + + /// Configuration for running moderation on the request input and generated output. + #[serde(skip_serializing_if = "Option::is_none")] + pub moderation: Option, + + /// How many chat completion choices to generate for each input message. Keep `n` as 1 to minimize costs. + #[serde(skip_serializing_if = "Option::is_none")] + pub n: Option, + + /// Whether to enable parallel function calling during tool use. + #[serde(skip_serializing_if = "Option::is_none")] + pub parallel_tool_calls: Option, + + /// Configuration for a Predicted Output, which can greatly improve response times when large parts of + /// the model response are known ahead of time. + #[serde(skip_serializing_if = "Option::is_none")] + pub prediction: Option, + + /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in + /// the text so far, increasing the model's likelihood to talk about new topics. + #[serde(skip_serializing_if = "Option::is_none")] + pub presence_penalty: Option, + + /// Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_key: Option, + + /// Options for prompt caching. Supported for `gpt-5.6` and later models. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_options: Option, + + /// Deprecated. Use `prompt_cache_options.ttl` instead. The retention policy for the prompt cache. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_retention: Option, + + /// Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in + /// faster responses and fewer tokens used on reasoning in a response. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + + /// An object specifying the format that the model must output. + #[serde(skip_serializing_if = "Option::is_none")] + pub response_format: Option, + + /// A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies. + #[serde(skip_serializing_if = "Option::is_none")] + pub safety_identifier: Option, + + /// If specified, our system will make a best effort to sample deterministically. + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + + /// Specifies the processing type used for serving the request. + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, + + /// Up to 4 sequences where the API will stop generating further tokens. + /// Not supported with latest reasoning models `o3` and `o4-mini`. + #[serde(skip_serializing_if = "Option::is_none")] + pub stop: Option, + + /// Whether or not to store the output of this chat completion request for use in model distillation or evals products. + #[serde(skip_serializing_if = "Option::is_none")] + pub store: Option, + + /// If set to true, the model response data will be streamed to the client as it is generated using server-sent events. + #[serde(skip_serializing_if = "Option::is_none")] + pub stream: Option, + + /// Options for streaming response. Only set this when you set `stream: true`. + #[serde(skip_serializing_if = "Option::is_none")] + pub stream_options: Option, + + /// What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random. + #[serde(skip_serializing_if = "Option::is_none")] + pub temperature: Option, + + /// Controls which (if any) tool is called by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_choice: Option, + + /// A list of tools the model may call. You can provide either custom tools or function tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub tools: Option>, + + /// An integer between 0 and 20 specifying the maximum number of most likely tokens to return at each token position. + /// `logprobs` must be set to `true` if this parameter is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub top_logprobs: Option, + + /// An alternative to sampling with temperature, called nucleus sampling, where the model considers the + /// results of the tokens with top_p probability mass. + #[serde(skip_serializing_if = "Option::is_none")] + pub top_p: Option, + + /// Deprecated in favor of `safety_identifier` and `prompt_cache_key`. A stable identifier for your end-users. + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + + /// Constrains the verbosity of the model's response. Supported values: `low`, `medium`, `high`. Default is `medium`. + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, + + /// This tool searches the web for relevant results to use in a response. + #[serde(skip_serializing_if = "Option::is_none")] + pub web_search_options: Option, +} + +// ============================================================================ +// Message Types +// ============================================================================ + +/// Represents a message in the conversation history passed to the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "role")] +pub enum ChatCompletionMessageParam { + /// Developer-provided instructions that the model should follow, regardless of messages sent by the user. + #[serde(rename = "developer")] + Developer(ChatCompletionDeveloperMessageParam), + + /// System instructions (superseded by developer role in newer models). + #[serde(rename = "system")] + System(ChatCompletionSystemMessageParam), + + /// Messages sent by an end user, containing prompts or additional context information. + #[serde(rename = "user")] + User(ChatCompletionUserMessageParam), + + /// Messages sent by the model in response to user messages. + #[serde(rename = "assistant")] + Assistant(ChatCompletionAssistantMessageParam), + + /// Tool response message replying to a tool call. + #[serde(rename = "tool")] + Tool(ChatCompletionToolMessageParam), + + /// Deprecated function call response message. + #[serde(rename = "function")] + Function(ChatCompletionFunctionMessageParam), +} + +/// Developer-provided instructions that the model should follow. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionDeveloperMessageParam { + /// The contents of the developer message. + pub content: DeveloperContent, + + /// An optional name for the participant. Provides the model information to differentiate + /// between participants of the same role. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// Content representation for developer messages (either a string or array of text content parts). +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum DeveloperContent { + /// Text string content. + Text(String), + /// Content parts with type text. + Parts(Vec), +} + +/// Developer-provided instructions that the model should follow (legacy `system` role). +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionSystemMessageParam { + /// The contents of the system message. + pub content: SystemContent, + + /// An optional name for the participant. Provides the model information to differentiate + /// between participants of the same role. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// Content representation for system messages (either a string or array of text content parts). +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum SystemContent { + /// Text string content. + Text(String), + /// Content parts with type text. + Parts(Vec), +} + +/// Messages sent by an end user, containing prompts or additional context information. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionUserMessageParam { + /// The contents of the user message. + pub content: UserContent, + + /// An optional name for the participant. Provides the model information to differentiate + /// between participants of the same role. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// Content representation for user messages (either a raw string or an array of diverse content parts). +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum UserContent { + /// Raw text content. + Text(String), + /// An array of content parts containing text, image, audio, or file inputs. + Parts(Vec), +} + +/// Content parts for user messages. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ChatCompletionContentPart { + /// Text content part. + #[serde(rename = "text")] + Text(ChatCompletionContentPartText), + + /// Image content part. + #[serde(rename = "image_url")] + ImageUrl(ChatCompletionContentPartImage), + + /// Audio input content part. + #[serde(rename = "input_audio")] + InputAudio(ChatCompletionContentPartInputAudio), + + /// File input content part. + #[serde(rename = "file")] + File(FileContentPart), +} + +/// Messages sent by the model in response to user messages. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionAssistantMessageParam { + /// Data about a previous audio response from the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub audio: Option, + + /// The contents of the assistant message. Required unless `tool_calls` or `function_call` is specified. + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + + /// Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called. + #[serde(skip_serializing_if = "Option::is_none")] + pub function_call: Option, + + /// An optional name for the participant. Provides the model information to differentiate between participants of the same role. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// The refusal message by the assistant. + #[serde(skip_serializing_if = "Option::is_none")] + pub refusal: Option, + + /// The tool calls generated by the model, such as function calls. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_calls: Option>, +} + +/// Data about a previous audio response from the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AssistantAudioParam { + /// Unique identifier for a previous audio response from the model. + pub id: String, +} + +/// Contents of an assistant message. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum AssistantContent { + /// Plain string content. + Text(String), + /// Content parts representing text or refusals. + Parts(Vec), +} + +/// Content parts supported inside assistant messages. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum AssistantContentPart { + /// Text content part. + #[serde(rename = "text")] + Text(ChatCompletionContentPartText), + /// Refusal message part generated by the model. + #[serde(rename = "refusal")] + Refusal(ChatCompletionContentPartRefusal), +} + +/// A tool response message responding to a previous tool call. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionToolMessageParam { + /// The contents of the tool message. + pub content: ToolContent, + + /// Tool call that this message is responding to. + pub tool_call_id: String, +} + +/// The content of a tool message. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ToolContent { + /// String content. + Text(String), + /// Array of text content parts. + Parts(Vec), +} + +/// Deprecated function message representation. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionFunctionMessageParam { + /// The contents of the function message. + pub content: Option, + + /// The name of the function to call. + pub name: String, +} + +// ============================================================================ +// Content Parts +// ============================================================================ + +/// Text content part. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionContentPartText { + /// The text content. + pub text: String, + + /// Marks the exact end of a reusable prompt prefix. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_breakpoint: Option, +} + +/// Image content part. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionContentPartImage { + /// The image details. + pub image_url: ImageUrl, + + /// Marks the exact end of a reusable prompt prefix. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_breakpoint: Option, +} + +/// Details for an image input. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ImageUrl { + /// Either a URL of the image or the base64 encoded image data. + pub url: String, + + /// Specifies the detail level of the image (`auto`, `low`, `high`). + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, +} + +/// Detail level for image processing. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ImageDetail { + /// Automatic resolution selection. + Auto, + /// Low resolution processing. + Low, + /// High resolution processing. + High, +} + +/// Audio content input part. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionContentPartInputAudio { + /// The audio details. + pub input_audio: InputAudio, + + /// Marks the exact end of a reusable prompt prefix. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_breakpoint: Option, +} + +/// Encoded input audio payload. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct InputAudio { + /// Base64 encoded audio data. + pub data: String, + + /// The format of the encoded audio data. Currently supports `wav` and `mp3`. + pub format: AudioInputFormat, +} + +/// Formats supported for audio input. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum AudioInputFormat { + /// WAV audio. + Wav, + /// MP3 audio. + Mp3, +} + +/// File content part. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct FileContentPart { + /// The file input payload. + pub file: FileData, + + /// Marks the exact end of a reusable prompt prefix. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_cache_breakpoint: Option, +} + +/// File reference or inline data. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct FileData { + /// The base64 encoded file data, used when passing the file to the model as a string. + #[serde(skip_serializing_if = "Option::is_none")] + pub file_data: Option, + + /// The ID of an uploaded file to use as input. + #[serde(skip_serializing_if = "Option::is_none")] + pub file_id: Option, + + /// The name of the file, used when passing the file to the model as a string. + #[serde(skip_serializing_if = "Option::is_none")] + pub filename: Option, +} + +/// Refusal message content part. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionContentPartRefusal { + /// The refusal message generated by the model. + pub refusal: String, +} + +/// Marks the exact end of a reusable prompt prefix. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PromptCacheBreakpoint { + /// The breakpoint mode. Always `explicit`. + pub mode: PromptCacheBreakpointMode, +} + +/// Mode for prompt cache breakpoint. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PromptCacheBreakpointMode { + /// Explicit breakpoint mode. + Explicit, +} + +// ============================================================================ +// Tool Calls & Definitions +// ============================================================================ + +/// Tool call generated by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ChatCompletionMessageToolCall { + /// A call to a function tool created by the model. + #[serde(rename = "function")] + Function(ChatCompletionMessageFunctionToolCall), + + /// A call to a custom tool created by the model. + #[serde(rename = "custom")] + Custom(ChatCompletionMessageCustomToolCall), +} + +/// A call to a function tool created by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionMessageFunctionToolCall { + /// The ID of the tool call. + pub id: String, + + /// The function that the model called. + pub function: FunctionCall, +} + +/// Function invocation details. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct FunctionCall { + /// The arguments to call the function with, as generated by the model in JSON format. + pub arguments: String, + + /// The name of the function to call. + pub name: String, +} + +/// A call to a custom tool created by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionMessageCustomToolCall { + /// The ID of the tool call. + pub id: String, + + /// The custom tool that the model called. + pub custom: CustomToolCall, +} + +/// Custom tool invocation details. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CustomToolCall { + /// The input for the custom tool call generated by the model. + pub input: String, + + /// The name of the custom tool to call. + pub name: String, +} + +/// A tool the model may call in requests. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ChatCompletionTool { + /// A standard function tool. + #[serde(rename = "function")] + Function(ChatCompletionFunctionTool), + + /// A custom tool with grammar/text format specifications. + #[serde(rename = "custom")] + Custom(ChatCompletionCustomTool), +} + +/// A function tool definition. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionFunctionTool { + /// The function definition. + pub function: FunctionDefinition, +} + +/// Definition of a function that can be called by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct FunctionDefinition { + /// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with max length 64. + pub name: String, + + /// A description of what the function does, used by the model to choose when and how to call the function. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// The parameters the functions accepts, described as a JSON Schema object. + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option, + + /// Whether to enable strict schema adherence when generating the function call. + #[serde(skip_serializing_if = "Option::is_none")] + pub strict: Option, +} + +/// A custom tool that processes input using a specified format. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionCustomTool { + /// Properties of the custom tool. + pub custom: CustomToolDefinition, +} + +/// Properties of a custom tool. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CustomToolDefinition { + /// The name of the custom tool, used to identify it in tool calls. + pub name: String, + + /// Optional description of the custom tool, used to provide more context. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// The input format for the custom tool. Default is unconstrained text. + #[serde(skip_serializing_if = "Option::is_none")] + pub format: Option, +} + +/// Format options for custom tool inputs. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum CustomToolFormat { + /// Unconstrained free-form text. + #[serde(rename = "text")] + Text, + + /// A grammar defined by the user. + #[serde(rename = "grammar")] + Grammar { + /// Grammar configuration. + grammar: CustomToolGrammar, + }, +} + +/// Grammar configuration for a custom tool. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CustomToolGrammar { + /// The grammar definition. + pub definition: String, + + /// The syntax of the grammar definition: `lark` or `regex`. + pub syntax: GrammarSyntax, +} + +/// Grammar syntax type. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum GrammarSyntax { + /// Lark grammar. + Lark, + /// Regular expression grammar. + Regex, +} + +// ============================================================================ +// Tool Choice & Function Call Options +// ============================================================================ + +/// Controls which (if any) function is called by the model (deprecated option). +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum FunctionCallOption { + /// Mode string: `"none"` or `"auto"`. + Mode(String), + /// Force a specific function call. + Named(NamedFunctionCall), +} + +/// Forces the model to call a specific function. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct NamedFunctionCall { + /// The name of the function to call. + pub name: String, +} + +/// Controls which (if any) tool is called by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ToolChoiceOption { + /// General modes: `none`, `auto`, or `required`. + Mode(ToolChoiceMode), + + /// Constrains the tools available to the model to a pre-defined set. + AllowedTools(ChatCompletionAllowedToolChoice), + + /// Specifies a specific function tool the model should use. + NamedFunction(ChatCompletionNamedToolChoice), + + /// Specifies a specific custom tool the model should use. + NamedCustom(ChatCompletionNamedToolChoiceCustom), +} + +/// Basic tool choice modes. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ToolChoiceMode { + /// Model will not call any tool and instead generates a message. + None, + /// Model can pick between generating a message or calling one or more tools. + Auto, + /// Model must call one or more tools. + Required, +} + +/// Constrains the tools available to the model to a pre-defined set. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionAllowedToolChoice { + /// Allowed tool configuration type. Always `allowed_tools`. + #[serde(rename = "type")] + pub r#type: String, + + /// The allowed tools configuration. + pub allowed_tools: ChatCompletionAllowedTools, +} + +/// Configuration container for allowed tools. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionAllowedTools { + /// Mode: `auto` allows model to pick from allowed tools; `required` requires calling one or more. + pub mode: ToolChoiceMode, + + /// A list of tool definitions that the model should be allowed to call. + pub tools: Vec, +} + +/// Specifies a particular function tool the model must call. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionNamedToolChoice { + /// Tool type. Always `function`. + #[serde(rename = "type")] + pub r#type: String, + + /// Function identifier. + pub function: NamedFunctionCall, +} + +/// Specifies a particular custom tool the model must call. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionNamedToolChoiceCustom { + /// Tool type. Always `custom`. + #[serde(rename = "type")] + pub r#type: String, + + /// Custom tool identifier. + pub custom: NamedCustomTool, +} + +/// Custom tool identifier container. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct NamedCustomTool { + /// The name of the custom tool to call. + pub name: String, +} + +// ============================================================================ +// Configuration Parameters (Audio, Caching, Moderation, etc.) +// ============================================================================ + +/// Parameters for audio output. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionAudioParam { + /// Specifies the output audio format. Must be one of `wav`, `aac`, `mp3`, `flac`, `opus`, or `pcm16`. + pub format: AudioOutputFormat, + + /// The voice the model uses to respond. Built-in voices or custom voice object `{ "id": "voice_1234" }`. + pub voice: AudioVoice, +} + +/// Formats available for generated audio output. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum AudioOutputFormat { + /// WAV format. + Wav, + /// AAC format. + Aac, + /// MP3 format. + Mp3, + /// FLAC format. + Flac, + /// Opus format. + Opus, + /// PCM 16-bit format. + Pcm16, +} + +/// Voice selection for audio response. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum AudioVoice { + /// Named voice identifier (e.g. `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, `cedar`). + Named(String), + /// Custom voice reference with an `id`. + Custom { + /// The custom voice ID. + id: String, + }, +} + +/// Modality types that can be output by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Modality { + /// Text response. + Text, + /// Audio response. + Audio, +} + +/// Moderation request configuration. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationConfig { + /// The moderation model to use, e.g. `omni-moderation-latest`. + pub model: String, + + /// The policy to apply to moderated response input and output. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy: Option, +} + +/// Input/output moderation policy settings. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationPolicy { + /// The moderation policy for the response input. + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + + /// The moderation policy for the response output. + #[serde(skip_serializing_if = "Option::is_none")] + pub output: Option, +} + +/// Policy mode configuration. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationModeConfig { + /// Moderation mode: `score` or `block`. + pub mode: ModerationPolicyMode, +} + +/// Moderation policy mode. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ModerationPolicyMode { + /// Return scores only without blocking. + Score, + /// Block request/response when flagged. + Block, +} + +/// Configuration for Predicted Outputs. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionPredictionContent { + /// The type of predicted content. Currently always `content`. + #[serde(rename = "type")] + pub r#type: String, + + /// The predicted content to match against. + pub content: PredictionContent, +} + +/// Predicted output content type. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum PredictionContent { + /// Text content. + Text(String), + /// Content parts. + Parts(Vec), +} + +/// Options for prompt caching. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PromptCacheOptions { + /// Controls whether OpenAI automatically creates an implicit cache breakpoint (`implicit` or `explicit`). + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + + /// The minimum lifetime applied to every cache breakpoint. Defaults to `30m`. + #[serde(skip_serializing_if = "Option::is_none")] + pub ttl: Option, +} + +/// Mode for prompt caching. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PromptCacheMode { + /// Automatically create one implicit cache breakpoint and up to 3 explicit. + Implicit, + /// Write up to 4 explicit breakpoints without creating an implicit one. + Explicit, +} + +/// Supported TTL for prompt caching. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum PromptCacheTtl { + /// 30 minutes cache TTL. + #[serde(rename = "30m")] + ThirtyMinutes, +} + +/// Prompt cache retention options (deprecated). +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum PromptCacheRetention { + /// In-memory cache retention. + #[serde(rename = "in_memory")] + InMemory, + /// Up to 24 hours retention. + #[serde(rename = "24h")] + TwentyFourHours, +} + +/// Constrains reasoning effort for reasoning models. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ReasoningEffort { + /// Disable reasoning. + None, + /// Minimal reasoning effort. + Minimal, + /// Low reasoning effort. + Low, + /// Medium reasoning effort. + Medium, + /// High reasoning effort. + High, + /// Extra high reasoning effort. + Xhigh, + /// Maximum reasoning effort. + Max, +} + +/// An object specifying the format that the model must output. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ResponseFormat { + /// Default plain text response format. + #[serde(rename = "text")] + Text, + + /// Structured Outputs format conforming to a JSON Schema. + #[serde(rename = "json_schema")] + JsonSchema { + /// Schema configuration options. + json_schema: JsonSchemaOptions, + }, + + /// Legacy JSON object mode ensuring output is valid JSON. + #[serde(rename = "json_object")] + JsonObject, +} + +/// Options for Structured Outputs JSON schema. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct JsonSchemaOptions { + /// The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, max length 64. + pub name: String, + + /// A description of what the response format is for. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// The schema for the response format, described as a JSON Schema object. + #[serde(skip_serializing_if = "Option::is_none")] + pub schema: Option, + + /// Whether to enable strict schema adherence. + #[serde(skip_serializing_if = "Option::is_none")] + pub strict: Option, +} + +/// Specifies the processing type used for serving the request. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ServiceTier { + /// Processed with the tier configured in Project settings. + Auto, + /// Processed with standard pricing and performance. + Default, + /// Flex Processing service tier. + Flex, + /// Scale processing tier. + Scale, + /// Priority tier. + Priority, + /// Fast mode processing tier. + Fast, +} + +/// Stop sequences where the API stops generating tokens. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum StopCondition { + /// A single stop sequence. + Single(String), + /// Up to 4 stop sequences. + Multiple(Vec), +} + +/// Options for streaming responses. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionStreamOptions { + /// When true, adds random characters to an `obfuscation` field to normalize payload sizes. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_obfuscation: Option, + + /// If set, an additional chunk containing usage statistics will be streamed before the `data: [DONE]` message. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_usage: Option, +} + +/// Constrains the verbosity of the model's response. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Verbosity { + /// More concise responses. + Low, + /// Standard verbosity response (default). + Medium, + /// More verbose responses. + High, +} + +/// Web search tool options. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct WebSearchOptions { + /// High-level guidance for the amount of context window space to use for search (`low`, `medium`, `high`). + #[serde(skip_serializing_if = "Option::is_none")] + pub search_context_size: Option, + + /// Approximate location parameters for the search. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_location: Option, +} + +/// Context window space size to allocate for web search. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum SearchContextSize { + /// Low context size. + Low, + /// Medium context size (default). + Medium, + /// High context size. + High, +} + +/// Approximate user location for web search queries. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UserLocation { + /// Location details container. + pub approximate: ApproximateLocation, + + /// Type of location approximation. Always `approximate`. + #[serde(rename = "type")] + pub r#type: String, +} + +/// Geographic details for web search localization. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ApproximateLocation { + /// Free text city name, e.g. "San Francisco". + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option, + + /// Two-letter ISO country code, e.g. "US". + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option, + + /// Free text region name, e.g. "California". + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option, + + /// IANA timezone string, e.g. "America/Los_Angeles". + #[serde(skip_serializing_if = "Option::is_none")] + pub timezone: Option, +} + +// ============================================================================ +// Response: Chat Completion +// ============================================================================ + +/// Represents a chat completion response returned by model, based on the provided input. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletion { + /// A unique identifier for the chat completion. + pub id: String, + + /// A list of chat completion choices. Can be more than one if `n` is greater than 1. + pub choices: Vec, + + /// The Unix timestamp (in seconds) of when the chat completion was created. + pub created: i64, + + /// The model used for the chat completion. + pub model: String, + + /// The object type, which is always `chat.completion`. + pub object: String, + + /// Set of 16 key-value pairs attached to an object. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + + /// Moderation results for the request input and generated output, if requested. + #[serde(skip_serializing_if = "Option::is_none")] + pub moderation: Option, + + /// Specifies the processing tier actually used for serving the request. + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, + + /// This fingerprint represents the backend configuration that the model runs with. + #[serde(skip_serializing_if = "Option::is_none")] + pub system_fingerprint: Option, + + /// Usage statistics for the completion request. + #[serde(skip_serializing_if = "Option::is_none")] + pub usage: Option, +} + +/// A choice option generated by the model. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionChoice { + /// The reason the model stopped generating tokens. + pub finish_reason: FinishReason, + + /// The index of the choice in the list of choices. + pub index: u32, + + /// Log probability information for the choice. + #[serde(skip_serializing_if = "Option::is_none")] + pub logprobs: Option, + + /// A chat completion message generated by the model. + pub message: ChatCompletionResponseMessage, +} + +/// Reason the model stopped generating tokens. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum FinishReason { + /// Model hit a natural stop point or a provided stop sequence. + Stop, + /// Maximum number of tokens specified in request was reached. + Length, + /// The model called a tool. + ToolCalls, + /// Content was omitted due to a flag from content filters. + ContentFilter, + /// Deprecated function call finished. + FunctionCall, +} + +/// Log probability information for a choice. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionChoiceLogprobs { + /// A list of message content tokens with log probability information. + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option>, + + /// A list of message refusal tokens with log probability information. + #[serde(skip_serializing_if = "Option::is_none")] + pub refusal: Option>, +} + +/// Token log probability information. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionTokenLogprob { + /// The token string. + pub token: String, + + /// A list of integers representing the UTF-8 bytes representation of the token. + #[serde(skip_serializing_if = "Option::is_none")] + pub bytes: Option>, + + /// The log probability of this token. + pub logprob: f64, + + /// List of the most likely tokens and their log probability at this position. + #[serde(default)] + pub top_logprobs: Vec, +} + +/// Top log probability entry. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct TopLogprob { + /// The token string. + pub token: String, + + /// The UTF-8 bytes representation. + #[serde(skip_serializing_if = "Option::is_none")] + pub bytes: Option>, + + /// The log probability of this token. + pub logprob: f64, +} + +/// Message generated by the model in the response. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionResponseMessage { + /// The role of the author of this message. Always `assistant`. + pub role: String, + + /// The contents of the message. + pub content: Option, + + /// The refusal message generated by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub refusal: Option, + + /// Annotations for the message (e.g. citations from web search). + #[serde(skip_serializing_if = "Option::is_none")] + pub annotations: Option>, + + /// If audio modality was requested, contains audio response data. + #[serde(skip_serializing_if = "Option::is_none")] + pub audio: Option, + + /// Deprecated function call generated by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub function_call: Option, + + /// The tool calls generated by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_calls: Option>, +} + +/// Response annotations. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ChatCompletionAnnotation { + /// URL citation annotation from web search. + #[serde(rename = "url_citation")] + UrlCitation { + /// Citation details. + url_citation: UrlCitation, + }, +} + +/// Details of a web search citation. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UrlCitation { + /// The index of the last character of the URL citation in the message. + pub end_index: usize, + + /// The index of the first character of the URL citation in the message. + pub start_index: usize, + + /// The title of the web resource. + pub title: String, + + /// The URL of the web resource. + pub url: String, +} + +/// Audio response data. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionAudio { + /// Unique identifier for this audio response. + pub id: String, + + /// Base64 encoded audio bytes generated by the model. + pub data: String, + + /// Unix timestamp (in seconds) for when this audio response expires. + pub expires_at: i64, + + /// Transcript of the audio generated by the model. + pub transcript: String, +} + +// ============================================================================ +// Moderation Response +// ============================================================================ + +/// Moderation results returned for input and generated output. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionModerationResponse { + /// Moderation result or error for request input. + pub input: ModerationEvaluation, + + /// Moderation result or error for generated output. + pub output: ModerationEvaluation, +} + +/// Evaluation of moderation (either success results or an error). +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ModerationEvaluation { + /// Successful moderation results. + #[serde(rename = "moderation_results")] + Results(ModerationResults), + + /// Error produced while attempting moderation. + #[serde(rename = "error")] + Error(ModerationError), +} + +/// Successful moderation results container. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationResults { + /// The moderation model used. + pub model: String, + + /// A list of moderation results. + pub results: Vec, +} + +/// Individual item evaluated by moderation. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationResultItem { + /// Dictionary of moderation categories to flags (true if flagged). + pub categories: HashMap, + + /// Which modalities of input are reflected by the score for each category. + pub category_applied_input_types: HashMap>, + + /// Dictionary of moderation categories to scores. + pub category_scores: HashMap, + + /// A boolean indicating whether the content was flagged by any category. + pub flagged: bool, + + /// The moderation model that produced this result. + pub model: String, +} + +/// Moderation error object. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ModerationError { + /// Error code. + pub code: String, + + /// Error message. + pub message: String, +} + +// ============================================================================ +// Usage +// ============================================================================ + +/// Usage statistics for the completion request. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CompletionUsage { + /// Number of tokens in the generated completion. + pub completion_tokens: u32, + + /// Number of tokens in the prompt. + pub prompt_tokens: u32, + + /// Total number of tokens used in the request (prompt + completion). + pub total_tokens: u32, + + /// Breakdown of tokens used in a completion. + #[serde(skip_serializing_if = "Option::is_none")] + pub completion_tokens_details: Option, + + /// Breakdown of tokens used in the prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_tokens_details: Option, +} + +/// Breakdown of tokens used in the completion. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CompletionTokensDetails { + /// When using Predicted Outputs, tokens in prediction that appeared in completion. + #[serde(skip_serializing_if = "Option::is_none")] + pub accepted_prediction_tokens: Option, + + /// Audio tokens generated by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_tokens: Option, + + /// Tokens generated by the model for reasoning. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_tokens: Option, + + /// When using Predicted Outputs, tokens in prediction that did not appear in completion. + #[serde(skip_serializing_if = "Option::is_none")] + pub rejected_prediction_tokens: Option, + + /// Text output tokens generated by the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub text_tokens: Option, +} + +/// Breakdown of tokens used in the prompt. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PromptTokensDetails { + /// Audio input tokens present in the prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_tokens: Option, + + /// The unadjusted number of prompt tokens written to cache. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_tokens: Option, + + /// Cached tokens present in the prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub cached_tokens: Option, + + /// Image input tokens present in the prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub image_tokens: Option, + + /// Text input tokens present in the prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub text_tokens: Option, +} + +// ============================================================================ +// Streaming Chunk Types +// ============================================================================ + +/// Represents a streamed chunk of a chat completion (`chat.completion.chunk`). +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionChunk { + /// A unique identifier for the chat completion. + pub id: String, + + /// A list of chat completion chunk choices. + pub choices: Vec, + + /// The Unix timestamp (in seconds) of when the chunk was created. + pub created: i64, + + /// The model used to generate the chunk. + pub model: String, + + /// The object type, which is always `chat.completion.chunk`. + pub object: String, + + /// The system fingerprint for the backend configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub system_fingerprint: Option, + + /// Specifies the processing tier used. + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, + + /// Token usage statistics, present in the final chunk when `include_usage` is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub usage: Option, +} + +/// A choice entry in a streamed chunk. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionChunkChoice { + /// The index of the choice in the list of choices. + pub index: u32, + + /// A chunk of message content generated by the model. + pub delta: ChatCompletionChunkDelta, + + /// Reason why the model finished generating, if this is the final chunk for this choice. + #[serde(skip_serializing_if = "Option::is_none")] + pub finish_reason: Option, + + /// Log probabilities for the current chunk. + #[serde(skip_serializing_if = "Option::is_none")] + pub logprobs: Option, +} + +/// The incremental message contents generated in a streaming chunk. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ChatCompletionChunkDelta { + /// The role of the author, if being established in this delta. + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + + /// The incremental text content. + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + + /// The refusal message content delta, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub refusal: Option, + + /// Incremental tool call information. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_calls: Option>, + + /// Obfuscation noise payload if stream obfuscation is active. + #[serde(skip_serializing_if = "Option::is_none")] + pub obfuscation: Option, +} diff --git a/src/openai/chat/mod.rs b/src/openai/chat/mod.rs new file mode 100644 index 0000000..6b31b14 --- /dev/null +++ b/src/openai/chat/mod.rs @@ -0,0 +1 @@ +pub mod completion; \ No newline at end of file diff --git a/src/openai/mod.rs b/src/openai/mod.rs new file mode 100644 index 0000000..30a62fc --- /dev/null +++ b/src/openai/mod.rs @@ -0,0 +1 @@ +pub mod chat; diff --git a/src/server.rs b/src/server.rs index 2b1b35b..1356249 100644 --- a/src/server.rs +++ b/src/server.rs @@ -17,28 +17,110 @@ pub struct AppState { } #[derive(Deserialize, TypedPath)] -#[typed_path("/v1/chat/completions")] -struct V1ChatCompletions; +#[typed_path("/chat/completions")] +struct ChatCompletionsPath; + +enum Provider { + CloudflareWorkersAi, + GoogleAiStudio, + Groq, + HuggingFace, + Mistral, + OpenRouter, +} #[derive(Debug, Serialize, Deserialize)] enum Shortcut { - Xs, + XS, S, M, L, - XL + XL, } -async fn post_v1_chat_completions( - _: V1ChatCompletions, +impl Shortcut { + fn suggestions(&self) -> Vec<(Provider, String)> { + use Provider as P; + use Shortcut as S; + + match self { + S::XS => vec![ + (P::GoogleAiStudio, "gemini-3.5-flash-lite".into()), + (P::OpenRouter, "poolside/laguna-xs-2.1:free".into()), + (P::OpenRouter, "nex-agi/nex-n2.5-mini:free".into()), + (P::OpenRouter, "cohere/north-mini-code:free".into()), + (P::OpenRouter, "nvidia/nemotron-3.5-lightning:free".into()), + (P::OpenRouter, "google/gemma-4-26b-a4b-it:free".into()), + (P::OpenRouter, "poolside/laguna-xs.2:free".into()), + (P::OpenRouter, "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free".into()), + (P::CloudflareWorkersAi, "@cf/openai/gpt-oss-20b".into()), + (P::OpenRouter, "openai/gpt-oss-20b:free".into()), + (P::Groq, "openai/gpt-oss-20b".into()), + (P::OpenRouter, "liquid/lfm-2.5-2.6b:free".into()), + (P::Groq, "groq/compound-mini".into()), + (P::OpenRouter, "openrouter/free".into()), + ], + S::S => vec![ + (P::CloudflareWorkersAi, "@cf/qwen/qwen3.8-27b".into()), + (P::OpenRouter, "qwen/qwen3.8-27b:free".into()), + (P::Groq, "qwen/qwen3.8-27b".into()), + (P::HuggingFace, "Qwen/Qwen3.8-27B".into()), + (P::HuggingFace, "meta-models/Muse-Glimmer-30B".into()), + (P::OpenRouter, "google/gemma-4-31b-it:free".into()), + (P::Mistral, "mistral-small-2603".into()), + (P::Groq, "groq/compound".into()), + (P::OpenRouter, "openrouter/fusion".into()), + ], + S::M => vec![ + (P::GoogleAiStudio, "gemini-3.8-flash".into()), + (P::GoogleAiStudio, "gemini-3.7-flash".into()), + (P::Mistral, "mistral-medium-3.5".into()), + (P::OpenRouter, "inclusionai/ling-3.0-flash-vl:free".into()), + (P::OpenRouter, "poolside/laguna-s-2.1:free".into()), + (P::OpenRouter, "nvidia/nemotron-3-super-120b-a12b:free".into()), + (P::CloudflareWorkersAi, "@cf/openai/gpt-oss-120b".into()), + (P::OpenRouter, "openai/gpt-oss-120b:free".into()), + (P::Groq, "openai/gpt-oss-120b".into()), + ], + S::L => vec![ + (P::CloudflareWorkersAi, "@cf/zai-org/glm-5.3-flash".into()), + (P::HuggingFace, "zai-org/GLM-5.3-Flash".into()), + (P::HuggingFace, "deepseek-ai/DeepSeek-V4.1-Flash".into()), + (P::OpenRouter, "nex-agi/nex-n2.5-pro:free".into()), + (P::OpenRouter, "thinkingmachines/inkling-small:free".into()), + (P::HuggingFace, "thinkingmachines/Inkling-Small".into()) + ], + S::XL => vec![ + (P::CloudflareWorkersAi, "@cf/zai-org/glm-5.3".into()), + (P::HuggingFace, "zai-org/GLM-5.3".into()), + (P::Mistral, "zai-glm-5-3".into()), + (P::HuggingFace, "moonshotai/Kimi-K3".into()), + (P::HuggingFace, "Qwen/Qwen3.8-2.4T-A95B".into()), + (P::HuggingFace, "MiniMaxAI/MiniMax-M3".into()), + (P::OpenRouter, "thinkingmachines/inkling:free".into()), + (P::HuggingFace, "thinkingmachines/Inkling".into()), + ], + } + } +} + +enum ChatCompletionResponse { + Immediate(openai::chat::completion::ChatCompletion), + Stream(()), +} + +async fn create_chat_completion( + _: ChatCompletionsPath, State(state): State, - Json(request): Json>, -) -> Result, ()> { - todo!(); + Json(request): Json>, +) -> Result { + if request.stream.unwrap_or(false) { + } else { + } } pub fn create_router() -> Router { - Router::new().typed_post(post_v1_chat_completions) + Router::new().typed_post(create_chat_completion) } pub fn create_app(state: AppState) -> Router<()> {