3274 lines · 115.6 KB · Rust
RawDownload
use anyhow::{Result, anyhow};use collections::HashMap;use futures::{Stream, StreamExt};use language_model_core::{    LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelImage,    LanguageModelRequest, LanguageModelRequestMessage, LanguageModelToolChoice,    LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, MessageContent,    Role, StopReason, TokenUsage,    util::{fix_streamed_json, parse_tool_arguments},};use std::pin::Pin;use std::sync::Arc; use crate::responses::{    Request as ResponseRequest, 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,    }} 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>,    reasoning_effort: Option<ReasoningEffort>,    interleaved_reasoning: bool,) -> crate::Request {    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::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 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(&tool_use.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(),                    });                }            }        }    }     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: 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| crate::ToolDefinition::Function {                function: FunctionDefinition {                    name: tool.name,                    description: Some(tool.description),                    parameters: Some(tool.input_schema),                },            })            .collect(),        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,    } = request;     let service_tier = service_tier_for(speed);     let mut input_items = Vec::new();    let mut replayed_reasoning_item_indexes = HashMap::default();    for (index, message) in messages.into_iter().enumerate() {        append_message_to_response_items(            message,            index,            &mut replayed_reasoning_item_indexes,            &mut input_items,        );    }     let tools: Vec<_> = tools        .into_iter()        .map(|tool| crate::responses::ToolDefinition::Function {            name: tool.name,            description: Some(tool.description),            parameters: Some(tool.input_schema),            strict: None,        })        .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,    }} fn append_message_to_response_items(    message: LanguageModelRequestMessage,    index: usize,    replayed_reasoning_item_indexes: &mut HashMap<String, usize>,    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::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();                input_items.push(ResponseInputItem::FunctionCall(ResponseFunctionCallItem {                    call_id,                    name: tool_use.name.to_string(),                    arguments: 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)                    }                };                input_items.push(ResponseInputItem::FunctionCallOutput(                    ResponseFunctionCallOutputItem {                        call_id: tool_result.tool_use_id.to_string(),                        output,                    },                ));            }        }    }     flush_response_parts(        &role,        index,        phase.as_deref(),        &mut content_parts,        input_items,    );} fn append_reasoning_details_to_response_items(    reasoning_details: Option<&serde_json::Value>,    replayed_reasoning_item_indexes: &mut HashMap<String, usize>,    input_items: &mut Vec<ResponseInputItem>,) {    let Some(reasoning_details) = reasoning_details else {        return;    };     let Some(metadata) = response_message_metadata_from_details(reasoning_details) else {        return;    };     for reasoning_item in metadata.reasoning_items {        push_replayed_reasoning_item(reasoning_item, replayed_reasoning_item_indexes, input_items);    }} fn push_replayed_reasoning_item(    reasoning_item: ResponseReasoningInputItem,    replayed_reasoning_item_indexes: &mut HashMap<String, usize>,    input_items: &mut Vec<ResponseInputItem>,) {    if let Some(id) = reasoning_item.id.as_ref() {        if let Some(index) = replayed_reasoning_item_indexes.get(id) {            input_items[*index] = ResponseInputItem::Reasoning(reasoning_item);            return;        }         replayed_reasoning_item_indexes.insert(id.clone(), input_items.len());    }     input_items.push(ResponseInputItem::Reasoning(reasoning_item));} fn push_response_text_part(    role: &Role,    text: impl Into<String>,    parts: &mut Vec<ResponseInputContent>,) {    let text = text.into();    if text.trim().is_empty() {        return;    }     match role {        Role::Assistant => parts.push(ResponseInputContent::OutputText {            text,            annotations: Vec::new(),        }),        _ => parts.push(ResponseInputContent::Text { text }),    }} fn push_response_image_part(    role: &Role,    image: LanguageModelImage,    parts: &mut Vec<ResponseInputContent>,) {    match role {        Role::Assistant => parts.push(ResponseInputContent::OutputText {            text: "[image omitted]".to_string(),            annotations: Vec::new(),        }),        _ => parts.push(ResponseInputContent::Image {            image_url: image.to_base64_url(),        }),    }} fn flush_response_parts(    role: &Role,    _index: usize,    phase: Option<&str>,    parts: &mut Vec<ResponseInputContent>,    input_items: &mut Vec<ResponseInputItem>,) {    if parts.is_empty() {        return;    }     let item = ResponseInputItem::Message(ResponseMessageItem {        role: match role {            Role::User => crate::Role::User,            Role::Assistant => crate::Role::Assistant,            Role::System => crate::Role::System,        },        content: parts.clone(),        phase: match role {            Role::Assistant => phase.map(str::to_string),            Role::User | Role::System => None,        },    });

Lines 1–500 of 3274.