983 lines · 31.4 KB · Rust
RawDownload
pub mod batches;pub mod completion;pub mod responses; use anyhow::{Context as _, Result, anyhow};use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};use http_client::{    AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt,    StatusCode,    http::{HeaderMap, HeaderValue},};pub use language_model_core::ReasoningEffort;use serde::{Deserialize, Serialize};use serde_json::Value;use std::{convert::TryFrom, future::Future};use strum::EnumIter;use thiserror::Error; pub const OPEN_AI_API_URL: &str = "https://api.openai.com/v1"; fn is_none_or_empty<T: AsRef<[U]>, U>(opt: &Option<T>) -> bool {    opt.as_ref().is_none_or(|v| v.as_ref().is_empty())} #[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]#[serde(rename_all = "lowercase")]pub enum Role {    User,    Assistant,    System,    Tool,} impl TryFrom<String> for Role {    type Error = anyhow::Error;     fn try_from(value: String) -> Result<Self> {        match value.as_str() {            "user" => Ok(Self::User),            "assistant" => Ok(Self::Assistant),            "system" => Ok(Self::System),            "tool" => Ok(Self::Tool),            _ => anyhow::bail!("invalid role '{value}'"),        }    }} impl From<Role> for String {    fn from(val: Role) -> Self {        match val {            Role::User => "user".to_owned(),            Role::Assistant => "assistant".to_owned(),            Role::System => "system".to_owned(),            Role::Tool => "tool".to_owned(),        }    }} #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]pub enum Model {    #[serde(rename = "gpt-4")]    Four,    #[serde(rename = "gpt-4o-mini")]    FourOmniMini,    #[serde(rename = "o3")]    O3,    #[serde(rename = "gpt-5")]    Five,    #[serde(rename = "gpt-5-mini")]    FiveMini,    #[serde(rename = "gpt-5-nano")]    FiveNano,    #[serde(rename = "gpt-5.1")]    FivePointOne,    #[serde(rename = "gpt-5.2")]    FivePointTwo,    #[serde(rename = "gpt-5.3-codex")]    FivePointThreeCodex,    #[serde(rename = "gpt-5.4-nano")]    FivePointFourNano,    #[serde(rename = "gpt-5.4-mini")]    FivePointFourMini,    #[serde(rename = "gpt-5.4")]    FivePointFour,    #[serde(rename = "gpt-5.4-pro")]    FivePointFourPro,    #[serde(rename = "gpt-5.5")]    FivePointFive,    #[serde(rename = "gpt-5.5-pro")]    FivePointFivePro,    #[serde(rename = "gpt-5.6-sol")]    #[default]    FivePointSixSol,    #[serde(rename = "gpt-5.6-terra")]    FivePointSixTerra,    #[serde(rename = "gpt-5.6-luna")]    FivePointSixLuna,    #[serde(rename = "custom")]    Custom {        name: String,        /// The name displayed in the UI, such as in the agent panel model dropdown menu.        display_name: Option<String>,        max_tokens: u64,        max_output_tokens: Option<u64>,        max_completion_tokens: Option<u64>,        reasoning_effort: Option<ReasoningEffort>,        #[serde(default = "default_supports_chat_completions")]        supports_chat_completions: bool,        #[serde(default = "default_supports_images")]        supports_images: bool,    },} const fn default_supports_chat_completions() -> bool {    true} const fn default_supports_images() -> bool {    true} impl Model {    pub fn default_fast() -> Self {        Self::FivePointSixLuna    }     pub fn from_id(id: &str) -> Result<Self> {        match id {            "gpt-4" => Ok(Self::Four),            "gpt-4o-mini" => Ok(Self::FourOmniMini),            "o3" => Ok(Self::O3),            "gpt-5" => Ok(Self::Five),            "gpt-5-mini" => Ok(Self::FiveMini),            "gpt-5-nano" => Ok(Self::FiveNano),            "gpt-5.1" => Ok(Self::FivePointOne),            "gpt-5.2" => Ok(Self::FivePointTwo),            "gpt-5.3-codex" => Ok(Self::FivePointThreeCodex),            "gpt-5.4-nano" => Ok(Self::FivePointFourNano),            "gpt-5.4-mini" => Ok(Self::FivePointFourMini),            "gpt-5.4" => Ok(Self::FivePointFour),            "gpt-5.4-pro" => Ok(Self::FivePointFourPro),            "gpt-5.5" => Ok(Self::FivePointFive),            "gpt-5.5-pro" => Ok(Self::FivePointFivePro),            "gpt-5.6-sol" => Ok(Self::FivePointSixSol),            "gpt-5.6-terra" => Ok(Self::FivePointSixTerra),            "gpt-5.6-luna" => Ok(Self::FivePointSixLuna),            invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),        }    }     pub fn id(&self) -> &str {        match self {            Self::Four => "gpt-4",            Self::FourOmniMini => "gpt-4o-mini",            Self::O3 => "o3",            Self::Five => "gpt-5",            Self::FiveMini => "gpt-5-mini",            Self::FiveNano => "gpt-5-nano",            Self::FivePointOne => "gpt-5.1",            Self::FivePointTwo => "gpt-5.2",            Self::FivePointThreeCodex => "gpt-5.3-codex",            Self::FivePointFourNano => "gpt-5.4-nano",            Self::FivePointFourMini => "gpt-5.4-mini",            Self::FivePointFour => "gpt-5.4",            Self::FivePointFourPro => "gpt-5.4-pro",            Self::FivePointFive => "gpt-5.5",            Self::FivePointFivePro => "gpt-5.5-pro",            Self::FivePointSixSol => "gpt-5.6-sol",            Self::FivePointSixTerra => "gpt-5.6-terra",            Self::FivePointSixLuna => "gpt-5.6-luna",            Self::Custom { name, .. } => name,        }    }     pub fn display_name(&self) -> &str {        match self {            Self::Four => "gpt-4",            Self::FourOmniMini => "gpt-4o-mini",            Self::O3 => "o3",            Self::Five => "gpt-5",            Self::FiveMini => "gpt-5-mini",            Self::FiveNano => "gpt-5-nano",            Self::FivePointOne => "gpt-5.1",            Self::FivePointTwo => "gpt-5.2",            Self::FivePointThreeCodex => "gpt-5.3-codex",            Self::FivePointFourNano => "gpt-5.4-nano",            Self::FivePointFourMini => "gpt-5.4-mini",            Self::FivePointFour => "gpt-5.4",            Self::FivePointFourPro => "gpt-5.4-pro",            Self::FivePointFive => "gpt-5.5",            Self::FivePointFivePro => "gpt-5.5-pro",            Self::FivePointSixSol => "gpt-5.6-sol",            Self::FivePointSixTerra => "gpt-5.6-terra",            Self::FivePointSixLuna => "gpt-5.6-luna",            Self::Custom { display_name, .. } => display_name.as_deref().unwrap_or(&self.id()),        }    }     pub fn max_token_count(&self) -> u64 {        match self {            Self::Four => 8_192,            Self::FourOmniMini => 128_000,            Self::O3 => 200_000,            Self::Five => 272_000,            Self::FiveMini => 400_000,            Self::FiveNano => 400_000,            Self::FivePointOne => 400_000,            Self::FivePointTwo => 400_000,            Self::FivePointThreeCodex => 400_000,            Self::FivePointFourNano => 400_000,            Self::FivePointFourMini => 400_000,            Self::FivePointFour => 1_050_000,            Self::FivePointFourPro => 1_050_000,            Self::FivePointFive => 1_050_000,            Self::FivePointFivePro => 1_050_000,            Self::FivePointSixSol => 1_050_000,            Self::FivePointSixTerra => 1_050_000,            Self::FivePointSixLuna => 1_050_000,            Self::Custom { max_tokens, .. } => *max_tokens,        }    }     pub fn max_output_tokens(&self) -> Option<u64> {        match self {            Self::Custom {                max_output_tokens, ..            } => *max_output_tokens,            Self::Four => Some(8_192),            Self::FourOmniMini => Some(16_384),            Self::O3 => Some(100_000),            Self::Five => Some(128_000),            Self::FiveMini => Some(128_000),            Self::FiveNano => Some(128_000),            Self::FivePointOne => Some(128_000),            Self::FivePointTwo => Some(128_000),            Self::FivePointThreeCodex => Some(128_000),            Self::FivePointFourNano => Some(128_000),            Self::FivePointFourMini => Some(128_000),            Self::FivePointFour => Some(128_000),            Self::FivePointFourPro => Some(128_000),            Self::FivePointFive => Some(128_000),            Self::FivePointFivePro => Some(128_000),            Self::FivePointSixSol => Some(128_000),            Self::FivePointSixTerra => Some(128_000),            Self::FivePointSixLuna => Some(128_000),        }    }     pub fn reasoning_effort(&self) -> Option<ReasoningEffort> {        match self {            Self::Custom {                reasoning_effort, ..            } => reasoning_effort.to_owned(),            Self::FivePointOne            | Self::FivePointTwo            | Self::FivePointFour            | Self::FivePointFourMini            | Self::FivePointFourNano => Some(ReasoningEffort::None),            Self::FivePointSixSol => Some(ReasoningEffort::Low),            Self::O3            | Self::Five            | Self::FiveMini            | Self::FiveNano            | Self::FivePointThreeCodex            | Self::FivePointFourPro            | Self::FivePointFive            | Self::FivePointFivePro            | Self::FivePointSixTerra            | Self::FivePointSixLuna => Some(ReasoningEffort::Medium),            _ => None,        }    }     pub fn supported_reasoning_efforts(&self) -> &'static [ReasoningEffort] {        match self {            Self::Custom {                reasoning_effort: Some(effort),                ..            } => match effort {                ReasoningEffort::None => &[ReasoningEffort::None],                ReasoningEffort::Minimal => &[ReasoningEffort::Minimal],                ReasoningEffort::Low => &[ReasoningEffort::Low],                ReasoningEffort::Medium => &[ReasoningEffort::Medium],                ReasoningEffort::High => &[ReasoningEffort::High],                ReasoningEffort::XHigh => &[ReasoningEffort::XHigh],                ReasoningEffort::Max => &[ReasoningEffort::Max],            },            Self::O3 => &[                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,            ],            Self::FivePointOne => &[                ReasoningEffort::None,                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,            ],            Self::Five | Self::FiveMini | Self::FiveNano => &[                ReasoningEffort::Minimal,                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,            ],            Self::FivePointFourPro | Self::FivePointFivePro => &[                ReasoningEffort::Medium,                ReasoningEffort::High,                ReasoningEffort::XHigh,            ],            Self::FivePointThreeCodex => &[                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,                ReasoningEffort::XHigh,            ],            Self::FivePointSixSol | Self::FivePointSixTerra | Self::FivePointSixLuna => &[                ReasoningEffort::None,                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,                ReasoningEffort::XHigh,                ReasoningEffort::Max,            ],            Self::FivePointTwo            | Self::FivePointFour            | Self::FivePointFive            | Self::FivePointFourMini            | Self::FivePointFourNano => &[                ReasoningEffort::None,                ReasoningEffort::Low,                ReasoningEffort::Medium,                ReasoningEffort::High,                ReasoningEffort::XHigh,            ],            _ => &[],        }    }     pub fn uses_responses_api(&self) -> bool {        match self {            Self::Custom {                supports_chat_completions,                ..            } => !*supports_chat_completions,            _ => true,        }    }     /// Returns whether the given model supports the `parallel_tool_calls` parameter.    ///    /// If the model does not support the parameter, do not pass it up, or the API will return an error.    pub fn supports_parallel_tool_calls(&self) -> bool {        match self {            Self::Four            | Self::FourOmniMini            | Self::Five            | Self::FiveMini            | Self::FivePointOne            | Self::FivePointTwo            | Self::FivePointThreeCodex            | Self::FivePointFour            | Self::FivePointFourMini            | Self::FivePointFourNano            | Self::FivePointFourPro            | Self::FivePointFive            | Self::FivePointFivePro            | Self::FivePointSixSol            | Self::FivePointSixTerra            | Self::FivePointSixLuna            | Self::FiveNano => true,            Self::O3 | Model::Custom { .. } => false,        }    }     /// Returns whether the given model supports the `prompt_cache_key` parameter.    ///    /// If the model does not support the parameter, do not pass it up.    pub fn supports_prompt_cache_key(&self) -> bool {        true    }     /// Whether this model supports server-side compaction via the    /// `context_management` request parameter. OpenAI doesn't publish a    /// support matrix, but the GPT-5.5 guide notes compaction is a feature    /// shared with GPT-5.4, and the compaction docs exercise the GPT-5.3    /// Codex line, so we treat everything from GPT-5.3 onward as supported.    ///    /// <https://developers.openai.com/api/docs/guides/compaction>    pub fn supports_compaction(&self) -> bool {        match self {            Self::FivePointThreeCodex            | Self::FivePointFourNano            | Self::FivePointFourMini            | Self::FivePointFour            | Self::FivePointFourPro            | Self::FivePointFive            | Self::FivePointFivePro            | Self::FivePointSixSol            | Self::FivePointSixTerra            | Self::FivePointSixLuna => true,            Self::Four            | Self::FourOmniMini            | Self::O3            | Self::Five            | Self::FiveMini            | Self::FiveNano            | Self::FivePointOne            | Self::FivePointTwo            | Self::Custom { .. } => false,        }    }     /// Whether OpenAI's Priority processing tier is available for this model.    /// Sourced from <https://openai.com/api-priority-processing/>. The `*-pro`,    /// `*-nano`, and legacy `gpt-4` variants are not eligible.    pub fn supports_priority(&self) -> bool {        match self {            Self::FourOmniMini            | Self::O3            | Self::Five            | Self::FiveMini            | Self::FivePointOne            | Self::FivePointTwo            | Self::FivePointThreeCodex            | Self::FivePointFourMini            | Self::FivePointFour            | Self::FivePointFive            | Self::FivePointSixSol            | Self::FivePointSixTerra            | Self::FivePointSixLuna => true,            Self::Four            | Self::FiveNano            | Self::FivePointFourNano            | Self::FivePointFourPro            | Self::FivePointFivePro            | Self::Custom { .. } => false,        }    }} #[cfg(test)]mod tests {    use super::{Model, ReasoningEffort};     #[test]    fn gpt_5_1_uses_none_reasoning_by_default() {        let expected_efforts = [            ReasoningEffort::None,            ReasoningEffort::Low,            ReasoningEffort::Medium,            ReasoningEffort::High,        ];         assert_eq!(            Model::FivePointOne.reasoning_effort(),            Some(ReasoningEffort::None)        );        assert_eq!(            Model::FivePointOne.supported_reasoning_efforts(),            expected_efforts.as_slice()        );    }     #[test]    fn newer_frontier_models_support_none_reasoning() {        let expected_efforts = [            ReasoningEffort::None,            ReasoningEffort::Low,            ReasoningEffort::Medium,            ReasoningEffort::High,            ReasoningEffort::XHigh,        ];         assert_eq!(            Model::FivePointTwo.reasoning_effort(),            Some(ReasoningEffort::None)        );        assert_eq!(            Model::FivePointTwo.supported_reasoning_efforts(),            expected_efforts.as_slice()        );        assert_eq!(            Model::FivePointFour.reasoning_effort(),            Some(ReasoningEffort::None)        );        assert_eq!(            Model::FivePointFour.supported_reasoning_efforts(),            expected_efforts.as_slice()        );        assert_eq!(            Model::FivePointFive.reasoning_effort(),            Some(ReasoningEffort::Medium)        );        assert_eq!(            Model::FivePointFive.supported_reasoning_efforts(),            expected_efforts.as_slice()        );    } 

Lines 1–500 of 983.