omnillm 0.1.5

Production-grade LLM API gateway with multi-key load balancing, per-key rate limiting, circuit breaking, and cost tracking
Documentation
use serde_json::{json, Value};

use crate::types::{LlmResponse, Message, MessageRole, ResponseItem, TokenUsage, VendorExtensions};

use super::super::common::{
    assistant_message_from_response, collect_message_text, finish_reason_string,
    parse_finish_reason, response_item_as_message, response_items_from_message,
    response_output_items,
};
use super::super::{ProtocolError, ProviderProtocol};
use super::helpers::*;

pub(in crate::protocol) fn parse_openai_responses_response(
    body: &Value,
) -> Result<LlmResponse, ProtocolError> {
    let output: Vec<ResponseItem> = body
        .get("output")
        .and_then(Value::as_array)
        .map(|items| parse_openai_responses_output(items))
        .transpose()?
        .unwrap_or_default();
    let messages = output
        .iter()
        .filter_map(response_item_as_message)
        .cloned()
        .collect::<Vec<_>>();
    let content_text = body
        .get("output_text")
        .and_then(Value::as_str)
        .map(str::to_owned)
        .unwrap_or_else(|| collect_message_text(&messages));
    let usage = TokenUsage {
        prompt_tokens: body
            .get("usage")
            .and_then(|value| value.get("input_tokens"))
            .and_then(Value::as_u64)
            .unwrap_or(0) as u32,
        completion_tokens: body
            .get("usage")
            .and_then(|value| value.get("output_tokens"))
            .and_then(Value::as_u64)
            .unwrap_or(0) as u32,
        total_tokens: body
            .get("usage")
            .and_then(|value| value.get("total_tokens"))
            .and_then(Value::as_u64)
            .map(|value| value as u32),
        prompt_cache: parse_openai_prompt_cache_usage(body.get("usage")),
    };

    Ok(LlmResponse {
        output,
        messages,
        content_text,
        usage,
        model: body
            .get("model")
            .and_then(Value::as_str)
            .unwrap_or("unknown")
            .to_string(),
        provider_protocol: ProviderProtocol::OpenAiResponses,
        finish_reason: body.get("status").and_then(parse_finish_reason),
        response_id: body.get("id").and_then(Value::as_str).map(str::to_owned),
        vendor_extensions: VendorExtensions::new(),
    })
}

pub(in crate::protocol) fn emit_openai_responses_response(
    response: &LlmResponse,
) -> Result<Value, ProtocolError> {
    let output = response_output_items(response)
        .into_iter()
        .map(openai_responses_output_item)
        .collect::<Result<Vec<_>, _>>()?;
    Ok(json!({
        "id": response.response_id,
        "model": response.model,
        "status": response.finish_reason.as_ref().map(finish_reason_string),
        "output_text": response.content_text,
        "output": output,
        "usage": openai_responses_usage_json(&response.usage)
    }))
}

pub(in crate::protocol) fn parse_openai_chat_response(
    body: &Value,
) -> Result<LlmResponse, ProtocolError> {
    let choice = body
        .get("choices")
        .and_then(Value::as_array)
        .and_then(|choices| choices.first())
        .ok_or_else(|| ProtocolError::MissingField("choices[0]".into()))?;
    let message = parse_openai_chat_message(choice.get("message").unwrap_or(&Value::Null))?;
    let mut output = response_items_from_message(&message);
    if output.is_empty() {
        output.push(ResponseItem::Message {
            message: message.clone(),
        });
    }
    Ok(LlmResponse {
        output,
        messages: vec![message.clone()],
        content_text: message.plain_text(),
        usage: TokenUsage {
            prompt_tokens: body
                .get("usage")
                .and_then(|value| value.get("prompt_tokens"))
                .and_then(Value::as_u64)
                .unwrap_or(0) as u32,
            completion_tokens: body
                .get("usage")
                .and_then(|value| value.get("completion_tokens"))
                .and_then(Value::as_u64)
                .unwrap_or(0) as u32,
            total_tokens: body
                .get("usage")
                .and_then(|value| value.get("total_tokens"))
                .and_then(Value::as_u64)
                .map(|value| value as u32),
            prompt_cache: parse_openai_prompt_cache_usage(body.get("usage")),
        },
        model: body
            .get("model")
            .and_then(Value::as_str)
            .unwrap_or("unknown")
            .to_string(),
        provider_protocol: ProviderProtocol::OpenAiChatCompletions,
        finish_reason: choice.get("finish_reason").and_then(parse_finish_reason),
        response_id: body.get("id").and_then(Value::as_str).map(str::to_owned),
        vendor_extensions: VendorExtensions::new(),
    })
}

pub(in crate::protocol) fn emit_openai_chat_response(
    response: &LlmResponse,
) -> Result<Value, ProtocolError> {
    let message = assistant_message_from_response(response)
        .unwrap_or_else(|| Message::text(MessageRole::Assistant, response.content_text.clone()));
    Ok(json!({
        "id": response.response_id,
        "model": response.model,
        "choices": [{
            "index": 0,
            "finish_reason": response.finish_reason.as_ref().map(finish_reason_string),
            "message": openai_chat_message_json(message)?,
        }],
        "usage": openai_chat_usage_json(&response.usage)
    }))
}