1264 lines · 42.0 KB · Rust
RawDownload
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,};use serde::{Deserialize, Serialize, ser::SerializeSeq as _};use serde_json::Value;use std::sync::Arc; use crate::{ReasoningEffort, RequestError, Role, ServiceTier, ToolChoice};use language_model_core::{    CompactedContext, LanguageModelProviderId, ProviderCompactionState, SharedString,}; pub const COMPACTION_STATE_FORMAT: &str = "openai.responses.input-items.v1"; #[derive(Serialize, Debug)]pub struct Request {    pub model: String,    #[serde(skip_serializing_if = "Option::is_none")]    pub instructions: Option<String>,    #[serde(skip_serializing_if = "ResponseInput::is_empty")]    pub input: ResponseInput,    #[serde(skip_serializing_if = "Vec::is_empty")]    pub include: Vec<ResponseIncludable>,    #[serde(default)]    pub stream: bool,    #[serde(skip_serializing_if = "Option::is_none")]    pub temperature: Option<f32>,    #[serde(skip_serializing_if = "Option::is_none")]    pub top_p: Option<f32>,    #[serde(skip_serializing_if = "Option::is_none")]    pub max_output_tokens: Option<u64>,    #[serde(skip_serializing_if = "Option::is_none")]    pub parallel_tool_calls: Option<bool>,    #[serde(skip_serializing_if = "Option::is_none")]    pub tool_choice: Option<ToolChoice>,    #[serde(skip_serializing_if = "Vec::is_empty")]    pub tools: Vec<ToolDefinition>,    #[serde(skip_serializing_if = "Option::is_none")]    pub prompt_cache_key: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    pub reasoning: Option<ReasoningConfig>,    #[serde(skip_serializing_if = "Option::is_none")]    pub store: Option<bool>,    #[serde(skip_serializing_if = "Option::is_none")]    pub service_tier: Option<ServiceTier>,    #[serde(default, skip_serializing_if = "Option::is_none")]    pub context_management: Option<Vec<ContextManagement>>,} impl Request {    pub fn into_compact_request(self) -> CompactRequest {        CompactRequest {            model: self.model,            instructions: self.instructions,            input: self.input,            prompt_cache_key: self.prompt_cache_key,            service_tier: self.service_tier,        }    }} #[derive(Serialize, Debug)]pub struct CompactRequest {    pub model: String,    #[serde(skip_serializing_if = "Option::is_none")]    pub instructions: Option<String>,    pub input: ResponseInput,    #[serde(skip_serializing_if = "Option::is_none")]    pub prompt_cache_key: Option<String>,    #[serde(skip_serializing_if = "Option::is_none")]    pub service_tier: Option<ServiceTier>,} #[derive(Deserialize, Debug)]pub struct CompactedResponse {    pub id: String,    pub created_at: u64,    pub object: String,    pub output: Vec<Value>,    pub usage: ResponseUsage,} impl CompactedResponse {    pub fn into_compacted_context(        self,        owner: LanguageModelProviderId,    ) -> Result<CompactedContext> {        Ok(CompactedContext::ProviderState(            provider_compaction_state_from_items(owner, self.output)?,        ))    }} /// Packages a canonical replacement window into opaque provider state owned by/// `owner`.////// Several backends speak the OpenAI Responses protocol (OpenAI itself, Zed/// Cloud's OpenAI models, OpenAI-compatible endpoints, and others), but their/// encrypted compaction items are not interchangeable: only the backend that/// produced an item can decrypt it. The owner recorded here is what/// [`provider_compaction_items`] later compares against, so it must identify/// the backend whose infrastructure produced the items, not merely the wire/// protocol.pub fn provider_compaction_state_from_items(    owner: LanguageModelProviderId,    items: Vec<Value>,) -> Result<ProviderCompactionState> {    validate_compaction_items(&items)?;    Ok(ProviderCompactionState::new(        owner,        SharedString::new_static(COMPACTION_STATE_FORMAT),        serde_json::to_string(&items)?,    ))} /// Recovers the canonical replacement window from `state` if it is owned by/// `owner`, or `None` when the state belongs to a different backend and the/// caller should fall back to replaying the full transcript.pub fn provider_compaction_items(    state: &ProviderCompactionState,    owner: &LanguageModelProviderId,) -> Result<Option<Vec<Value>>> {    if state.provider_id() != owner {        return Ok(None);    }    if state.format() != COMPACTION_STATE_FORMAT {        return Err(anyhow!(            "unsupported OpenAI compaction state format: {}",            state.format()        ));    }     let items = serde_json::from_str::<Vec<Value>>(state.payload())        .context("malformed OpenAI compaction state payload")?;    validate_compaction_items(&items)?;    Ok(Some(items))} fn validate_compaction_items(items: &[Value]) -> Result<()> {    if items.is_empty() {        return Err(anyhow!("OpenAI returned an empty compaction output"));    }    if !items.iter().any(|item| {        item.get("type")            .and_then(Value::as_str)            .is_some_and(|item_type| item_type == "compaction")    }) {        return Err(anyhow!(            "OpenAI compaction output did not contain a compaction item"        ));    }    Ok(())} #[derive(Debug, Default)]pub struct ResponseInput {    provider_items: Vec<Value>,    generated_items: Vec<ResponseInputItem>,} impl ResponseInput {    pub fn new(provider_items: Vec<Value>, generated_items: Vec<ResponseInputItem>) -> Self {        Self {            provider_items,            generated_items,        }    }     pub fn is_empty(&self) -> bool {        self.provider_items.is_empty() && self.generated_items.is_empty()    }     /// Filters only the items this crate generated from the request.    ///    /// Provider items are a canonical replacement window that must be replayed    /// verbatim, so they are exempt from filtering. Callers that rewrite the    /// input to satisfy backend-specific requirements (and therefore can't    /// tolerate arbitrary items inside a replayed window) should not accept    /// provider-native compaction state in the first place.    pub fn retain(&mut self, predicate: impl FnMut(&ResponseInputItem) -> bool) {        self.generated_items.retain(predicate);    }} impl Serialize for ResponseInput {    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    where        S: serde::Serializer,    {        let mut sequence = serializer            .serialize_seq(Some(self.provider_items.len() + self.generated_items.len()))?;        for item in &self.provider_items {            sequence.serialize_element(item)?;        }        for item in &self.generated_items {            sequence.serialize_element(item)?;        }        sequence.end()    }} /// Server-side context management configuration.////// <https://developers.openai.com/api/docs/guides/compaction>#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ContextManagement {    Compaction { compact_threshold: u64 },} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]#[serde(rename_all = "snake_case")]pub enum ResponseIncludable {    #[serde(rename = "reasoning.encrypted_content")]    ReasoningEncryptedContent,} #[derive(Debug, Serialize, Deserialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ResponseInputItem {    Message(ResponseMessageItem),    FunctionCall(ResponseFunctionCallItem),    FunctionCallOutput(ResponseFunctionCallOutputItem),    CustomToolCall(ResponseCustomToolCallItem),    CustomToolCallOutput(ResponseCustomToolCallOutputItem),    Reasoning(ResponseReasoningInputItem),    Compaction(ResponseCompactionItem),} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct ResponseCompactionItem {    #[serde(default, skip_serializing_if = "Option::is_none")]    pub id: Option<Arc<str>>,    pub encrypted_content: Arc<str>,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseMessageItem {    pub role: Role,    pub content: Vec<ResponseInputContent>,    #[serde(default, skip_serializing_if = "Option::is_none")]    pub phase: Option<String>,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseFunctionCallItem {    pub call_id: String,    pub name: String,    pub arguments: String,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseFunctionCallOutputItem {    pub call_id: String,    pub output: ResponseFunctionCallOutputContent,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseCustomToolCallItem {    #[serde(default, skip_serializing_if = "Option::is_none")]    pub id: Option<String>,    pub call_id: String,    pub name: String,    pub input: String,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseCustomToolCallOutputItem {    pub call_id: String,    pub output: ResponseFunctionCallOutputContent,} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct ResponseReasoningInputItem {    #[serde(default, skip_serializing_if = "Option::is_none")]    pub id: Option<String>,    #[serde(default)]    pub summary: Vec<ResponseReasoningSummaryPart>,    #[serde(default, skip_serializing_if = "Vec::is_empty")]    pub content: Vec<Value>,    #[serde(default, skip_serializing_if = "Option::is_none")]    pub encrypted_content: Option<String>,    #[serde(default, skip_serializing_if = "Option::is_none")]    pub status: Option<String>,} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ResponseReasoningSummaryPart {    SummaryText { text: String },} #[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum ResponseFunctionCallOutputContent {    List(Vec<ResponseInputContent>),    Text(String),} #[derive(Debug, Clone, Serialize, Deserialize)]#[serde(tag = "type")]pub enum ResponseInputContent {    #[serde(rename = "input_text")]    Text { text: String },    #[serde(rename = "input_image")]    Image { image_url: String },    #[serde(rename = "output_text")]    OutputText {        text: String,        #[serde(default)]        annotations: Vec<serde_json::Value>,    },    #[serde(rename = "refusal")]    Refusal { refusal: String },} #[derive(Serialize, Debug)]pub struct ReasoningConfig {    pub effort: ReasoningEffort,    #[serde(skip_serializing_if = "Option::is_none")]    pub summary: Option<ReasoningSummaryMode>,} #[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]#[serde(rename_all = "lowercase")]pub enum ReasoningSummaryMode {    Auto,    Concise,    Detailed,} #[derive(Serialize, Deserialize, Debug)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ToolDefinition {    Function {        name: String,        #[serde(skip_serializing_if = "Option::is_none")]        description: Option<String>,        #[serde(skip_serializing_if = "Option::is_none")]        parameters: Option<Value>,        #[serde(skip_serializing_if = "Option::is_none")]        strict: Option<bool>,    },    Custom {        name: String,        #[serde(skip_serializing_if = "Option::is_none")]        description: Option<String>,        #[serde(skip_serializing_if = "Option::is_none")]        format: Option<CustomToolFormat>,    },} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]#[serde(tag = "type", rename_all = "snake_case")]pub enum CustomToolFormat {    Text,    Grammar {        syntax: CustomToolGrammarSyntax,        definition: String,    },} #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]#[serde(rename_all = "lowercase")]pub enum CustomToolGrammarSyntax {    Lark,    Regex,} #[derive(Serialize, Deserialize, Debug, Clone)]pub struct ResponseError {    #[serde(default)]    pub code: Option<String>,    pub message: String,    #[serde(default)]    pub param: Option<Value>,} /// Payload of the top-level `error` SSE event from the Responses API.////// OpenAI's spec documents the error fields as being at the top level of the/// event, but in practice the API often nests them under an `error` object.#[derive(Deserialize, Debug, Clone, Default)]pub struct GenericStreamErrorPayload {    #[serde(flatten)]    top_level: PartialResponseError,    #[serde(default)]    error: Option<PartialResponseError>,} #[derive(Deserialize, Debug, Clone, Default)]struct PartialResponseError {    #[serde(default)]    code: Option<String>,    #[serde(default)]    message: Option<String>,    #[serde(default)]    param: Option<Value>,} impl GenericStreamErrorPayload {    pub fn into_response_error(self) -> ResponseError {        let nested = self.error.unwrap_or_default();        ResponseError {            code: self.top_level.code.or(nested.code),            message: self                .top_level                .message                .or(nested.message)                .unwrap_or_default(),            param: self.top_level.param.or(nested.param),        }    }} #[derive(Deserialize, Debug)]#[serde(tag = "type")]pub enum StreamEvent {    #[serde(rename = "response.created")]    Created { response: ResponseSummary },    #[serde(rename = "response.in_progress")]    InProgress { response: ResponseSummary },    #[serde(rename = "response.output_item.added")]    OutputItemAdded {        output_index: usize,        #[serde(default)]        sequence_number: Option<u64>,        item: ResponseOutputItem,    },    #[serde(rename = "response.output_item.done")]    OutputItemDone {        output_index: usize,        #[serde(default)]        sequence_number: Option<u64>,        item: ResponseOutputItem,    },    #[serde(rename = "response.content_part.added")]    ContentPartAdded {        item_id: String,        output_index: usize,        content_index: usize,        part: Value,    },    #[serde(rename = "response.content_part.done")]    ContentPartDone {        item_id: String,        output_index: usize,        content_index: usize,        part: Value,    },    #[serde(rename = "response.output_text.delta")]    OutputTextDelta {        item_id: String,        output_index: usize,        #[serde(default)]        content_index: Option<usize>,        delta: String,    },    #[serde(rename = "response.output_text.done")]    OutputTextDone {        item_id: String,        output_index: usize,        #[serde(default)]        content_index: Option<usize>,        text: String,    },    #[serde(rename = "response.refusal.delta")]    RefusalDelta {        item_id: String,        output_index: usize,        content_index: usize,        delta: String,        #[serde(default)]        sequence_number: Option<u64>,    },    #[serde(rename = "response.refusal.done")]    RefusalDone {        item_id: String,        output_index: usize,        content_index: usize,        refusal: String,        #[serde(default)]        sequence_number: Option<u64>,    },    #[serde(rename = "response.reasoning_summary_part.added")]    ReasoningSummaryPartAdded {        item_id: String,        output_index: usize,        summary_index: usize,    },    #[serde(rename = "response.reasoning_summary_text.delta")]    ReasoningSummaryTextDelta {        item_id: String,        output_index: usize,        delta: String,    },    #[serde(rename = "response.reasoning_summary_text.done")]    ReasoningSummaryTextDone {

Lines 1–500 of 1264.