4113 lines · 147.1 KB · Rust
RawDownload
use anyhow::{Result, anyhow};use collections::HashMap;use futures::{Stream, StreamExt};use language_model_core::{    CompactionContent, LanguageModelCompletionError, LanguageModelCompletionEvent,    LanguageModelCustomToolFormat, LanguageModelCustomToolGrammarSyntax, LanguageModelImage,    LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestToolInput,    LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse,    LanguageModelToolUseId, LanguageModelToolUseInput, MessageContent, Role, StopReason,    TokenUsage,    util::{fix_streamed_json, is_context_window_exceeded_message, parse_tool_arguments},};use std::pin::Pin;use std::sync::Arc; use crate::responses::{    ContextManagement, Request as ResponseRequest, ResponseCompactionItem,    ResponseCustomToolCallItem, ResponseCustomToolCallOutputItem, ResponseError,    ResponseFunctionCallItem, ResponseFunctionCallOutputContent, ResponseFunctionCallOutputItem,    ResponseIncludable, ResponseInputContent, ResponseInputItem, ResponseMessageItem,    ResponseOutputItem, ResponseOutputMessage, ResponseReasoningInputItem, ResponseReasoningItem,    ResponseReasoningSummaryPart, ResponseSummary as ResponsesSummary,    ResponseUsage as ResponsesUsage, StreamEvent as ResponsesStreamEvent,};use crate::{    FunctionContent, FunctionDefinition, ImageUrl, MessagePart, ReasoningEffort,    ResponseStreamEvent, ServiceTier, ToolCall, ToolCallContent,}; const RESPONSE_MESSAGE_PHASE_COMMENTARY: &str = "commentary";const RESPONSE_MESSAGE_PHASE_FINAL_ANSWER: &str = "final_answer"; /// Translates the request's `Speed` into the corresponding OpenAI service tier./// Only `Fast` produces a value; `Standard` leaves the field unset so that the/// project's default tier applies.fn service_tier_for(speed: Option<language_model_core::Speed>) -> Option<ServiceTier> {    match speed? {        language_model_core::Speed::Fast => Some(ServiceTier::Priority),        language_model_core::Speed::Standard => None,    }} #[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum ChatCompletionMaxTokensParameter {    MaxCompletionTokens,    MaxTokens,} pub fn into_open_ai(    request: LanguageModelRequest,    model_id: &str,    supports_parallel_tool_calls: bool,    supports_prompt_cache_key: bool,    max_output_tokens: Option<u64>,    max_tokens_parameter: ChatCompletionMaxTokensParameter,    reasoning_effort: Option<ReasoningEffort>,    interleaved_reasoning: bool,) -> Result<crate::Request> {    if request        .tools        .iter()        .any(|tool| matches!(tool.input, LanguageModelRequestToolInput::Custom { .. }))    {        return Err(anyhow!(            "OpenAI Chat Completions does not support custom tools; use Responses API instead"        ));    }     let stream = !model_id.starts_with("o1-");    let service_tier = service_tier_for(request.speed);     let mut messages = Vec::new();    let mut current_reasoning: Option<String> = None;    for message in request.messages {        for content in message.content {            match content {                MessageContent::Thinking { text, .. } if interleaved_reasoning => {                    current_reasoning.get_or_insert_default().push_str(&text);                }                MessageContent::Text(text) | MessageContent::Thinking { text, .. } => {                    let should_add = if message.role == Role::User {                        // Including whitespace-only user messages can cause error with OpenAI compatible APIs                        // See https://github.com/zed-industries/zed/issues/40097                        !text.trim().is_empty()                    } else {                        !text.is_empty()                    };                    if should_add {                        add_message_content_part(                            MessagePart::Text { text },                            message.role,                            &mut messages,                        );                        if let Some(reasoning) = current_reasoning.take() {                            if let Some(crate::RequestMessage::Assistant {                                reasoning_content,                                ..                            }) = messages.last_mut()                            {                                *reasoning_content = Some(reasoning);                            }                        }                    }                }                MessageContent::RedactedThinking(_) | MessageContent::Compaction(_) => {}                MessageContent::Image(image) => {                    add_message_content_part(                        MessagePart::Image {                            image_url: ImageUrl {                                url: image.to_base64_url(),                                detail: None,                            },                        },                        message.role,                        &mut messages,                    );                }                MessageContent::ToolUse(tool_use) => {                    let LanguageModelToolUseInput::Json(input) = &tool_use.input else {                        return Err(anyhow!(                            "OpenAI Chat Completions cannot replay custom tool call `{}`",                            tool_use.name                        ));                    };                    let tool_call = ToolCall {                        id: tool_use.id.to_string(),                        content: ToolCallContent::Function {                            function: FunctionContent {                                name: tool_use.name.to_string(),                                arguments: serde_json::to_string(input).unwrap_or_default(),                            },                        },                    };                     if let Some(crate::RequestMessage::Assistant { tool_calls, .. }) =                        messages.last_mut()                    {                        tool_calls.push(tool_call);                    } else {                        messages.push(crate::RequestMessage::Assistant {                            content: None,                            tool_calls: vec![tool_call],                            reasoning_content: current_reasoning.take(),                        });                    }                }                MessageContent::ToolResult(tool_result) => {                    let content: Vec<MessagePart> = tool_result                        .content                        .iter()                        .map(|part| match part {                            LanguageModelToolResultContent::Text(text) => MessagePart::Text {                                text: text.to_string(),                            },                            LanguageModelToolResultContent::Image(image) => MessagePart::Image {                                image_url: ImageUrl {                                    url: image.to_base64_url(),                                    detail: None,                                },                            },                        })                        .collect();                     messages.push(crate::RequestMessage::Tool {                        content: content.into(),                        tool_call_id: tool_result.tool_use_id.to_string(),                    });                }            }        }    }     Ok(crate::Request {        model: model_id.into(),        messages,        stream,        stream_options: if stream {            Some(crate::StreamOptions::default())        } else {            None        },        stop: request.stop,        temperature: request.temperature.or(Some(1.0)),        max_completion_tokens: match max_tokens_parameter {            ChatCompletionMaxTokensParameter::MaxCompletionTokens => max_output_tokens,            ChatCompletionMaxTokensParameter::MaxTokens => None,        },        max_tokens: match max_tokens_parameter {            ChatCompletionMaxTokensParameter::MaxCompletionTokens => None,            ChatCompletionMaxTokensParameter::MaxTokens => max_output_tokens,        },        parallel_tool_calls: if supports_parallel_tool_calls && !request.tools.is_empty() {            Some(supports_parallel_tool_calls)        } else {            None        },        prompt_cache_key: if supports_prompt_cache_key {            request.thread_id        } else {            None        },        tools: request            .tools            .into_iter()            .map(|tool| match tool.input {                LanguageModelRequestToolInput::Function { input_schema, .. } => {                    Ok(crate::ToolDefinition::Function {                        function: FunctionDefinition {                            name: tool.name,                            description: Some(tool.description),                            parameters: Some(input_schema),                        },                    })                }                LanguageModelRequestToolInput::Custom { .. } => Err(anyhow!(                    "OpenAI Chat Completions does not support custom tools; use Responses API instead"                )),            })            .collect::<Result<_>>()?,        tool_choice: request.tool_choice.map(|choice| match choice {            LanguageModelToolChoice::Auto => crate::ToolChoice::Auto,            LanguageModelToolChoice::Any => crate::ToolChoice::Required,            LanguageModelToolChoice::None => crate::ToolChoice::None,        }),        reasoning_effort,        service_tier,    })} pub fn into_open_ai_response(    request: LanguageModelRequest,    model_id: &str,    supports_parallel_tool_calls: bool,    supports_prompt_cache_key: bool,    max_output_tokens: Option<u64>,    default_reasoning_effort: Option<ReasoningEffort>,    supports_none_reasoning_effort: bool,) -> ResponseRequest {    let stream = !model_id.starts_with("o1-");     let LanguageModelRequest {        thread_id,        prompt_id: _,        intent: _,        messages,        tools,        tool_choice,        stop: _,        temperature,        thinking_allowed,        thinking_effort,        speed,        compact_at_tokens,    } = request;     let service_tier = service_tier_for(speed);     let mut input_items = Vec::new();    let mut replayed_reasoning_item_indexes = HashMap::default();    let mut tool_use_kinds_by_id = HashMap::default();    for (index, message) in messages.into_iter().enumerate() {        append_message_to_response_items(            message,            index,            &mut replayed_reasoning_item_indexes,            &mut tool_use_kinds_by_id,            &mut input_items,        );    }     let tools: Vec<_> = tools        .into_iter()        .map(|tool| match tool.input {            LanguageModelRequestToolInput::Function { input_schema, .. } => {                crate::responses::ToolDefinition::Function {                    name: tool.name,                    description: Some(tool.description),                    parameters: Some(input_schema),                    strict: None,                }            }            LanguageModelRequestToolInput::Custom { format } => {                crate::responses::ToolDefinition::Custom {                    name: tool.name,                    description: if tool.description.is_empty() {                        None                    } else {                        Some(tool.description)                    },                    format: format.map(custom_tool_format_into_open_ai),                }            }        })        .collect();     let default_reasoning_effort =        default_reasoning_effort.filter(|effort| *effort != ReasoningEffort::None);    let reasoning_effort = if thinking_allowed {        thinking_effort            .as_deref()            .and_then(|effort| effort.parse::<ReasoningEffort>().ok())            .filter(|effort| *effort != ReasoningEffort::None)            .or(default_reasoning_effort)    } else if supports_none_reasoning_effort {        Some(ReasoningEffort::None)    } else {        None    };     let reasoning = reasoning_effort.map(|effort| crate::responses::ReasoningConfig {        effort,        summary: if effort == ReasoningEffort::None {            None        } else {            Some(crate::responses::ReasoningSummaryMode::Auto)        },    });     let include = if reasoning        .as_ref()        .is_some_and(|reasoning| reasoning.effort != ReasoningEffort::None)        || input_items            .iter()            .any(|item| matches!(item, ResponseInputItem::Reasoning(_)))    {        vec![ResponseIncludable::ReasoningEncryptedContent]    } else {        Vec::new()    };     ResponseRequest {        model: model_id.into(),        instructions: None,        input: input_items,        store: Some(false),        include,        stream,        temperature,        top_p: None,        max_output_tokens,        parallel_tool_calls: if tools.is_empty() {            None        } else {            Some(supports_parallel_tool_calls)        },        tool_choice: tool_choice.map(|choice| match choice {            LanguageModelToolChoice::Auto => crate::ToolChoice::Auto,            LanguageModelToolChoice::Any => crate::ToolChoice::Required,            LanguageModelToolChoice::None => crate::ToolChoice::None,        }),        tools,        prompt_cache_key: if supports_prompt_cache_key {            thread_id        } else {            None        },        reasoning,        service_tier,        context_management: compact_at_tokens            .map(|compact_threshold| vec![ContextManagement::Compaction { compact_threshold }]),    }} fn append_message_to_response_items(    message: LanguageModelRequestMessage,    index: usize,    replayed_reasoning_item_indexes: &mut HashMap<String, usize>,    tool_use_kinds_by_id: &mut HashMap<LanguageModelToolUseId, ReplayToolKind>,    input_items: &mut Vec<ResponseInputItem>,) {    let mut content_parts: Vec<ResponseInputContent> = Vec::new();     let LanguageModelRequestMessage {        role,        content,        reasoning_details,        ..    } = message;    let phase = if role == Role::Assistant {        response_message_phase_from_details(reasoning_details.as_deref())    } else {        None    };     if role == Role::Assistant {        append_reasoning_details_to_response_items(            reasoning_details.as_deref(),            replayed_reasoning_item_indexes,            input_items,        );    }     for content in content {        match content {            MessageContent::Text(text) => {                push_response_text_part(&role, text, &mut content_parts);            }            MessageContent::Thinking { .. } | MessageContent::RedactedThinking(_) => {}            MessageContent::Compaction(CompactionContent::Encrypted {                id,                encrypted_content,            }) => {                flush_response_parts(                    &role,                    index,                    phase.as_deref(),                    &mut content_parts,                    input_items,                );                input_items.push(ResponseInputItem::Compaction(ResponseCompactionItem {                    id,                    encrypted_content,                }));            }            // Summary compaction blocks come from other providers, and a            // Pending block is a streaming-only UI signal; neither is replayed.            MessageContent::Compaction(                CompactionContent::Summary { .. } | CompactionContent::Pending,            ) => {}            MessageContent::Image(image) => {                push_response_image_part(&role, image, &mut content_parts);            }            MessageContent::ToolUse(tool_use) => {                flush_response_parts(                    &role,                    index,                    phase.as_deref(),                    &mut content_parts,                    input_items,                );                let call_id = tool_use.id.to_string();                match tool_use.input {                    LanguageModelToolUseInput::Json(_) => {                        tool_use_kinds_by_id.insert(tool_use.id, ReplayToolKind::Function);                        input_items.push(ResponseInputItem::FunctionCall(                            ResponseFunctionCallItem {                                call_id,                                name: tool_use.name.to_string(),                                arguments: tool_use.raw_input,                            },                        ));                    }                    LanguageModelToolUseInput::Text(_) => {                        tool_use_kinds_by_id.insert(tool_use.id, ReplayToolKind::Custom);                        input_items.push(ResponseInputItem::CustomToolCall(                            ResponseCustomToolCallItem {                                id: None,                                call_id,                                name: tool_use.name.to_string(),                                input: tool_use.raw_input,                            },                        ));                    }                }            }            MessageContent::ToolResult(tool_result) => {                flush_response_parts(                    &role,                    index,                    phase.as_deref(),                    &mut content_parts,                    input_items,                );                let output = match tool_result.content.as_slice() {                    [LanguageModelToolResultContent::Text(text)] => {                        ResponseFunctionCallOutputContent::Text(text.to_string())                    }                    _ => {                        let parts = tool_result                            .content                            .into_iter()                            .map(|part| match part {                                LanguageModelToolResultContent::Text(text) => {                                    ResponseInputContent::Text {                                        text: text.to_string(),                                    }                                }                                LanguageModelToolResultContent::Image(image) => {                                    ResponseInputContent::Image {                                        image_url: image.to_base64_url(),                                    }                                }                            })                            .collect();                        ResponseFunctionCallOutputContent::List(parts)                    }                };                match tool_use_kinds_by_id.get(&tool_result.tool_use_id) {                    Some(ReplayToolKind::Custom) => {                        input_items.push(ResponseInputItem::CustomToolCallOutput(                            ResponseCustomToolCallOutputItem {                                call_id: tool_result.tool_use_id.to_string(),                                output,                            },                        ));                    }                    Some(ReplayToolKind::Function) | None => {                        input_items.push(ResponseInputItem::FunctionCallOutput(                            ResponseFunctionCallOutputItem {                                call_id: tool_result.tool_use_id.to_string(),

Lines 1–500 of 4113.