use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LlmRoute {
Public,
Private,
#[default]
Auto,
}
impl LlmRoute {
pub fn as_str(&self) -> &'static str {
match self {
LlmRoute::Public => "public",
LlmRoute::Private => "private",
LlmRoute::Auto => "auto",
}
}
}
impl ExecuteChatRequest {
#[must_use]
pub fn public(mut self) -> Self {
self.route = Some(LlmRoute::Public);
self
}
#[must_use]
pub fn private(mut self) -> Self {
self.route = Some(LlmRoute::Private);
self
}
#[must_use]
pub fn auto(mut self) -> Self {
self.route = Some(LlmRoute::Auto);
self
}
}
impl ExecuteBestModelRequest {
#[must_use]
pub fn public(mut self) -> Self {
self.route = Some(LlmRoute::Public);
self
}
#[must_use]
pub fn private(mut self) -> Self {
self.route = Some(LlmRoute::Private);
self
}
#[must_use]
pub fn auto(mut self) -> Self {
self.route = Some(LlmRoute::Auto);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fresh_request() -> ExecuteChatRequest {
ExecuteChatRequest {
messages: vec![ChatMessage {
role: "user".into(),
content: "hi".into(),
}],
model: None,
platform: None,
route: None,
execution_preference: None,
max_output_tokens: None,
correlation_id: None,
tools: None,
tool_choice: None,
temperature: None,
response_format: None,
}
}
#[test]
fn builder_shortcut_public() {
let req = fresh_request().public();
assert_eq!(req.route, Some(LlmRoute::Public));
}
#[test]
fn builder_shortcut_private() {
let req = fresh_request().private();
assert_eq!(req.route, Some(LlmRoute::Private));
}
#[test]
fn builder_shortcut_auto() {
let req = fresh_request().auto();
assert_eq!(req.route, Some(LlmRoute::Auto));
}
#[test]
fn builder_shortcuts_chain_and_overwrite() {
let req = fresh_request().public().private();
assert_eq!(req.route, Some(LlmRoute::Private));
}
#[test]
fn route_field_serialises_as_route_mode_on_wire() {
let req = fresh_request().private();
let json = serde_json::to_string(&req).expect("serialize");
assert!(
json.contains("\"route_mode\":\"private\""),
"wire field name must be route_mode, got: {}",
json
);
assert!(
!json.contains("\"route\""),
"must not also emit a `route` field, got: {}",
json
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProxyMode {
Public,
Private,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionPreference {
PreferLocal,
LocalOnly,
NetworkOnly,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: String,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteChatRequest {
pub messages: Vec<ChatMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(rename = "route_mode", default, skip_serializing_if = "Option::is_none")]
pub route: Option<LlmRoute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub execution_preference: Option<ExecutionPreference>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format: Option<serde_json::Value>,
}
pub type StreamChatRequest = ExecuteChatRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteBestModelRequest {
pub messages: Vec<ChatMessage>,
#[serde(rename = "route_mode", default, skip_serializing_if = "Option::is_none")]
pub route: Option<LlmRoute>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub execution_preference: Option<ExecutionPreference>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
}
pub type ExecuteRequest = ExecuteChatRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteChatResponse {
pub response_text: String,
pub platform: String,
pub model: String,
pub execution_route: ProxyMode,
pub via_node_id: String,
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub cost_sats: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StreamChatChunk {
Delta {
text: String,
},
End {
execution_route: ProxyMode,
via_node_id: String,
prompt_tokens: u32,
completion_tokens: u32,
cost_sats: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
correlation_id: Option<String>,
},
Error {
code: String,
message: String,
},
}