use modkit_macros::domain_model;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
#[domain_model]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Role {
User,
Assistant,
System,
}
#[domain_model]
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum ContentPart {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { file_id: String },
}
#[domain_model]
#[derive(Debug, Clone)]
pub struct LlmMessage {
pub role: Role,
pub content: Vec<ContentPart>,
}
impl LlmMessage {
#[must_use]
pub fn user(text: impl Into<String>) -> Self {
LlmMessage {
role: Role::User,
content: vec![ContentPart::Text { text: text.into() }],
}
}
#[must_use]
pub fn assistant(text: impl Into<String>) -> Self {
LlmMessage {
role: Role::Assistant,
content: vec![ContentPart::Text { text: text.into() }],
}
}
#[must_use]
pub fn user_with_image(text: impl Into<String>, file_id: impl Into<String>) -> Self {
LlmMessage {
role: Role::User,
content: vec![
ContentPart::Text { text: text.into() },
ContentPart::Image {
file_id: file_id.into(),
},
],
}
}
}
#[domain_model]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileSearchFilter {
Eq { key: String, value: String },
In { key: String, values: Vec<String> },
And(Vec<FileSearchFilter>),
Or(Vec<FileSearchFilter>),
}
impl FileSearchFilter {
#[must_use]
pub fn attachment_eq(id: uuid::Uuid) -> Self {
Self::Eq {
key: "attachment_id".to_owned(),
value: id.to_string(),
}
}
#[must_use]
pub fn attachment_in(ids: &[uuid::Uuid]) -> Self {
assert!(!ids.is_empty(), "attachment_in called with empty ids");
Self::In {
key: "attachment_id".to_owned(),
values: ids.iter().map(ToString::to_string).collect(),
}
}
}
#[domain_model]
#[derive(Debug, Clone)]
pub enum LlmTool {
FileSearch {
vector_store_ids: Vec<String>,
filters: Option<FileSearchFilter>,
max_num_results: Option<u32>,
},
WebSearch {
search_context_size: WebSearchContextSize,
},
CodeInterpreter { file_ids: Vec<String> },
Function {
name: String,
description: String,
parameters: serde_json::Value,
},
}
#[domain_model]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema)]
#[allow(clippy::struct_field_names)]
pub struct Usage {
pub input_tokens: i64,
pub output_tokens: i64,
#[serde(default, skip_serializing)]
#[schema(read_only)]
pub cache_read_input_tokens: i64,
#[serde(default, skip_serializing)]
#[schema(read_only)]
pub cache_write_input_tokens: i64,
#[serde(default, skip_serializing)]
#[schema(read_only)]
pub reasoning_tokens: i64,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct Citation {
pub source: CitationSource,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment_id: Option<String>,
pub snippet: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub span: Option<TextSpan>,
}
pub use mini_chat_sdk::models::WebSearchContextSize;
#[domain_model]
#[derive(Debug, Clone, Copy, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum CitationSource {
File,
Web,
}
#[domain_model]
#[derive(Debug, Clone, Copy, Serialize, ToSchema)]
pub struct TextSpan {
pub start: usize,
pub end: usize,
}
#[domain_model]
#[derive(Debug, Clone)]
pub struct AttachmentRef {
pub id: Uuid,
pub filename: String,
}
#[domain_model]
#[derive(Debug, Clone, Copy, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ToolPhase {
Start,
Done,
}
#[domain_model]
#[derive(Debug, Clone)]
pub struct ContextMessage {
pub role: Role,
pub content: String,
}