use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub use super::common::{
DreamConfiguration, PeerCardConfiguration, ReasoningConfiguration, SummaryConfiguration,
};
pub use super::dream::SessionQueueStatus;
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Session {
pub id: String,
pub is_active: bool,
pub workspace_id: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, serde_json::Value>,
#[serde(default)]
pub configuration: SessionConfiguration,
pub created_at: DateTime<Utc>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, bon::Builder)]
#[builder(on(String, into))]
#[builder(finish_fn = build)]
pub struct SessionCreate {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peers: Option<HashMap<String, SessionPeerConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub configuration: Option<SessionConfiguration>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, bon::Builder)]
#[builder(on(String, into))]
#[builder(finish_fn = build)]
pub struct SessionUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub configuration: Option<SessionConfiguration>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, bon::Builder)]
#[builder(on(String, into))]
#[builder(finish_fn = build)]
pub struct SessionGet {
#[serde(skip_serializing_if = "Option::is_none")]
pub filters: Option<HashMap<String, serde_json::Value>>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize)]
pub struct SessionMetadataSet {
pub metadata: HashMap<String, serde_json::Value>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize)]
pub struct SessionConfigurationSet {
pub configuration: HashMap<String, serde_json::Value>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct SessionConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningConfiguration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peer_card: Option<PeerCardConfiguration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<SummaryConfiguration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dream: Option<DreamConfiguration>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct SessionPeerConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub observe_me: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub observe_others: Option<bool>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, bon::Builder)]
#[builder(on(String, into))]
#[builder(finish_fn = build)]
pub struct SessionContextOptions {
#[serde(default = "default_true")]
#[builder(default = true)]
pub summary: bool,
#[serde(default)]
#[builder(default)]
pub limit_to_session: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peer_target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peer_perspective: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search_query: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search_top_k: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search_max_distance: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_most_frequent: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_conclusions: Option<u32>,
}
const SEARCH_TOP_K_MIN: u32 = 1;
const SEARCH_TOP_K_MAX: u32 = 100;
const SEARCH_MAX_DISTANCE_MIN: f64 = 0.0;
const SEARCH_MAX_DISTANCE_MAX: f64 = 1.0;
const MAX_CONCLUSIONS_MIN: u32 = 1;
const MAX_CONCLUSIONS_MAX: u32 = 100;
impl SessionContextOptions {
pub fn validate(&self) -> std::result::Result<(), crate::error::HonchoError> {
if self.peer_perspective.is_some() && self.peer_target.is_none() {
return Err(crate::error::HonchoError::Validation(
"peer_perspective requires peer_target to be set".into(),
));
}
if self.search_query.is_some() && self.peer_target.is_none() {
return Err(crate::error::HonchoError::Validation(
"search_query requires peer_target to be set".into(),
));
}
if let Some(k) = self.search_top_k
&& !(SEARCH_TOP_K_MIN..=SEARCH_TOP_K_MAX).contains(&k)
{
return Err(crate::error::HonchoError::Validation(format!(
"search_top_k must be between {SEARCH_TOP_K_MIN} and {SEARCH_TOP_K_MAX}"
)));
}
if let Some(d) = self.search_max_distance
&& !(SEARCH_MAX_DISTANCE_MIN..=SEARCH_MAX_DISTANCE_MAX).contains(&d)
{
return Err(crate::error::HonchoError::Validation(format!(
"search_max_distance must be between {SEARCH_MAX_DISTANCE_MIN} and {SEARCH_MAX_DISTANCE_MAX}"
)));
}
if let Some(m) = self.max_conclusions
&& !(MAX_CONCLUSIONS_MIN..=MAX_CONCLUSIONS_MAX).contains(&m)
{
return Err(crate::error::HonchoError::Validation(format!(
"max_conclusions must be between {MAX_CONCLUSIONS_MIN} and {MAX_CONCLUSIONS_MAX}"
)));
}
Ok(())
}
}
fn default_true() -> bool {
true
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SessionContext {
pub id: String,
pub messages: Vec<super::message::MessageResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<Summary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peer_representation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peer_card: Option<Vec<String>>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SessionSummaries {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub short_summary: Option<Summary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub long_summary: Option<Summary>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SummaryType {
Short,
Long,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Summary {
pub content: String,
pub message_id: String,
pub summary_type: SummaryType,
pub created_at: DateTime<Utc>,
pub token_count: u32,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, bon::Builder)]
#[builder(on(String, into))]
#[builder(finish_fn = build)]
pub struct SessionListOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub filters: Option<HashMap<String, serde_json::Value>>,
#[serde(default = "default_page")]
#[builder(default = default_page())]
pub page: u64,
#[serde(default = "default_size")]
#[builder(default = default_size())]
pub size: u64,
#[serde(default)]
#[builder(default)]
pub reverse: bool,
}
fn default_page() -> u64 {
1
}
fn default_size() -> u64 {
50
}
pub type SessionPage = super::pagination::Page<Session>;
pub trait IntoAssistantRef {
fn as_assistant_name(&self) -> &str;
}
impl IntoAssistantRef for &str {
fn as_assistant_name(&self) -> &str {
self
}
}
impl IntoAssistantRef for String {
fn as_assistant_name(&self) -> &str {
self.as_str()
}
}
impl IntoAssistantRef for &crate::Peer {
fn as_assistant_name(&self) -> &str {
self.id()
}
}
impl SessionContext {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn to_openai(&self, assistant: impl IntoAssistantRef) -> Vec<serde_json::Value> {
let assistant = assistant.as_assistant_name();
let mut result: Vec<serde_json::Value> = Vec::new();
if let Some(ref rep) = self.peer_representation {
result.push(serde_json::json!({
"role": "system",
"content": format!("<peer_representation>{rep}</peer_representation>"),
}));
}
if let Some(ref card) = self.peer_card {
result.push(serde_json::json!({
"role": "system",
"content": format!("<peer_card>[{}]</peer_card>", card.iter().map(|s| format!("'{}'", s.replace('\'', "\\'"))).collect::<Vec<_>>().join(", ")),
}));
}
if let Some(ref summary) = self.summary {
result.push(serde_json::json!({
"role": "system",
"content": format!("<summary>{}</summary>", summary.content),
}));
}
for message in &self.messages {
if message.peer_id == assistant {
result.push(serde_json::json!({
"role": "assistant",
"name": message.peer_id,
"content": message.content,
}));
} else {
result.push(serde_json::json!({
"role": "user",
"name": message.peer_id,
"content": message.content,
}));
}
}
result
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn to_anthropic(&self, assistant: impl IntoAssistantRef) -> Vec<serde_json::Value> {
let assistant = assistant.as_assistant_name();
let mut result: Vec<serde_json::Value> = Vec::new();
if let Some(ref rep) = self.peer_representation {
result.push(serde_json::json!({
"role": "user",
"content": format!("<peer_representation>{rep}</peer_representation>"),
}));
}
if let Some(ref card) = self.peer_card {
result.push(serde_json::json!({
"role": "user",
"content": format!("<peer_card>[{}]</peer_card>", card.iter().map(|s| format!("'{}'", s.replace('\'', "\\'"))).collect::<Vec<_>>().join(", ")),
}));
}
if let Some(ref summary) = self.summary {
result.push(serde_json::json!({
"role": "user",
"content": format!("<summary>{}</summary>", summary.content),
}));
}
for message in &self.messages {
if message.peer_id == assistant {
result.push(serde_json::json!({
"role": "assistant",
"content": message.content,
}));
} else {
result.push(serde_json::json!({
"role": "user",
"content": format!("{}: {}", message.peer_id, message.content),
}));
}
}
result
}
#[must_use]
pub fn len(&self) -> usize {
self.messages.len()
+ usize::from(self.summary.is_some())
+ usize::from(self.peer_representation.is_some())
+ usize::from(self.peer_card.is_some())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
&& self.summary.is_none()
&& self.peer_representation.is_none()
&& self.peer_card.is_none()
}
}