Skip to main content

llm/providers/openai_compatible/
mod.rs

1#![doc = include_str!(concat!(env!("OUT_DIR"), "/docs/openai_compatible.md"))]
2
3pub mod config;
4pub mod generic;
5pub mod streaming;
6pub mod types;
7
8use async_openai::types::chat::ChatCompletionStreamOptions;
9use schemars::Schema;
10use utils::ReasoningEffort;
11
12use crate::providers::openai::mappers::map_tools;
13use crate::{Context, LlmError};
14use types::CompatibleChatRequest;
15
16pub use config::AetherOpenAiConfig;
17pub use streaming::{create_custom_stream_generic, process_compatible_stream};
18pub use types::ChatCompletionStreamResponse;
19
20/// Selects what `prompt_cache_key` is sent to a LLM provider.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum PromptCacheKeySource {
23    /// Do not send a prompt cache key.
24    Omit,
25    /// Use a hash of the prompt prefix.
26    Prefix,
27    /// Use a stable session identifier.
28    SessionAffinity,
29}
30
31impl PromptCacheKeySource {
32    pub(crate) fn resolve(self, context: &Context) -> Option<&str> {
33        match self {
34            Self::Omit => None,
35            Self::Prefix => context.prompt_cache_key(),
36            Self::SessionAffinity => context.session_affinity_key(),
37        }
38    }
39}
40
41/// Build a chat completion request from a context
42///
43/// This is shared logic for OpenAI-compatible providers like `OpenRouter` and Z.ai.
44/// Uses `CompatibleChatRequest` which preserves `reasoning_content` on assistant messages.
45pub(crate) fn build_chat_request(
46    model: &str,
47    context: &Context,
48    tool_schema_transform: Option<fn(&mut Schema)>,
49) -> Result<CompatibleChatRequest, LlmError> {
50    let messages = types::map_messages(context.messages())?;
51    let tools =
52        if context.tools().is_empty() { None } else { Some(map_tools(context.tools(), tool_schema_transform)?) };
53    let settings = context.model_settings();
54
55    Ok(CompatibleChatRequest {
56        model: model.to_string(),
57        messages,
58        stream: Some(true),
59        tools,
60        stream_options: Some(ChatCompletionStreamOptions { include_usage: Some(true), include_obfuscation: None }),
61        reasoning_effort: match context.reasoning_effort() {
62            ReasoningEffort::Default => None,
63            ReasoningEffort::Disabled => Some("none"),
64            ReasoningEffort::Minimal => Some("minimal"),
65            ReasoningEffort::Low => Some("low"),
66            ReasoningEffort::Medium => Some("medium"),
67            ReasoningEffort::High => Some("high"),
68            ReasoningEffort::Xhigh => Some("xhigh"),
69            ReasoningEffort::Max => Some("max"),
70        },
71        temperature: settings.temperature,
72        top_p: settings.top_p,
73        max_tokens: settings.max_tokens,
74        prompt_cache_key: None,
75    })
76}