use cyrup_sugars::AsyncStream;
use cyrup_sugars::prelude::*;
use serde_json::Value;
use std::collections::HashMap;
pub trait IntoHashMap {
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str>;
}
pub trait IntoChunkHandler {
fn into_chunk_handler(
self,
) -> Box<dyn Fn(Result<ConversationChunk, String>) -> ConversationChunk + Send + Sync + 'static>;
}
impl<F> IntoHashMap for F
where
F: FnOnce() -> hashbrown::HashMap<&'static str, &'static str>,
{
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str> {
self()
}
}
impl IntoHashMap for hashbrown::HashMap<&'static str, &'static str> {
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str> {
self
}
}
impl<const N: usize> IntoHashMap for [(&'static str, &'static str); N] {
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str> {
self.into_iter().collect()
}
}
impl IntoHashMap for Vec<(&'static str, &'static str)> {
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str> {
self.into_iter().collect()
}
}
impl<F> IntoChunkHandler for F
where
F: Fn(Result<ConversationChunk, String>) -> ConversationChunk + Send + Sync + 'static,
{
fn into_chunk_handler(
self,
) -> Box<dyn Fn(Result<ConversationChunk, String>) -> ConversationChunk + Send + Sync + 'static>
{
Box::new(self)
}
}
pub use sugars_collections::hash_map;
pub struct JsonClosure<F>(F);
impl<F> JsonClosure<F> {
pub fn new(f: F) -> Self {
JsonClosure(f)
}
}
impl<F> From<JsonClosure<F>> for hashbrown::HashMap<&'static str, &'static str>
where
F: FnOnce() -> hashbrown::HashMap<&'static str, &'static str>,
{
fn from(val: JsonClosure<F>) -> Self {
(val.0)()
}
}
pub struct FluentAi;
#[macro_export]
macro_rules! json_object {
({ $($key:expr => $value:expr),* $(,)? }) => {
{
let mut map = ::hashbrown::HashMap::new();
$(
map.insert($key, $value);
)*
map
}
};
}
pub struct Context<T>(std::marker::PhantomData<T>);
pub struct File;
pub struct Files;
pub struct Directory;
pub struct Github;
impl<T> Context<T> {
pub fn of(_path: &str) -> Context<T> {
Context(std::marker::PhantomData)
}
pub fn glob(_pattern: &str) -> Context<T> {
Context(std::marker::PhantomData)
}
}
pub struct Tool<T>(std::marker::PhantomData<T>);
pub struct Perplexity;
pub struct NamedTool {
#[allow(dead_code)]
name: String,
}
impl<T> Tool<T> {
pub fn new<P>(_params: P) -> Tool<T>
where
P: IntoHashMap,
{
Tool(std::marker::PhantomData)
}
}
impl Tool<()> {
pub fn named(name: &str) -> NamedToolBuilder {
NamedToolBuilder {
name: name.to_string(),
bin_path: None,
description: None,
}
}
}
pub struct NamedToolBuilder {
#[allow(dead_code)]
name: String,
#[allow(dead_code)]
bin_path: Option<String>,
#[allow(dead_code)]
description: Option<String>,
}
impl NamedToolBuilder {
pub fn bin(mut self, path: &str) -> Self {
self.bin_path = Some(path.to_string());
self
}
pub fn description(mut self, desc: String) -> Box<dyn std::any::Any> {
self.description = Some(desc);
Box::new(())
}
}
pub struct Stdio;
pub struct Library {
#[allow(dead_code)]
name: String,
}
impl Library {
pub fn named(name: &str) -> Self {
Library {
name: name.to_string(),
}
}
}
pub struct AgentRoleBuilder {
#[allow(dead_code)]
name: String,
#[allow(dead_code)]
provider: Option<String>,
#[allow(dead_code)]
temperature: Option<f64>,
#[allow(dead_code)]
max_tokens: Option<u64>,
#[allow(dead_code)]
system_prompt: Option<String>,
#[allow(dead_code)]
contexts: Vec<Box<dyn std::any::Any>>,
#[allow(dead_code)]
tools: Vec<Box<dyn std::any::Any>>,
#[allow(dead_code)]
additional_params: Option<HashMap<String, Value>>,
#[allow(dead_code)]
metadata: Option<HashMap<String, Value>>,
#[allow(dead_code)]
memory: Option<Library>,
#[allow(dead_code, clippy::type_complexity)]
chunk_handler: Option<
Box<dyn Fn(Result<ConversationChunk, String>) -> ConversationChunk + Send + Sync + 'static>,
>,
}
#[derive(Debug, Clone, Copy)]
pub enum MessageRole {
User,
System,
Assistant,
}
#[derive(Debug, Clone)]
pub struct ConversationChunk {
pub content: String,
pub role: MessageRole,
error: Option<String>,
}
impl MessageChunk for ConversationChunk {
fn bad_chunk(error: String) -> Self {
ConversationChunk {
content: format!("Error: {}", error),
role: MessageRole::System,
error: Some(error),
}
}
fn error(&self) -> Option<&str> {
self.error.as_deref()
}
}
impl std::fmt::Display for ConversationChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}: {}", self.role, self.content)
}
}
pub struct Agent {
#[allow(dead_code)]
builder: AgentRoleBuilder,
#[allow(dead_code)]
history: Vec<(MessageRole, String)>,
}
pub struct Mistral;
impl Mistral {
pub const MAGISTRAL_SMALL: &'static str = "mistral-small";
}
impl FluentAi {
pub fn agent_role(name: impl Into<String>) -> AgentRoleBuilder {
AgentRoleBuilder {
name: name.into(),
provider: None,
temperature: None,
max_tokens: None,
system_prompt: None,
contexts: Vec::new(),
tools: Vec::new(),
additional_params: None,
metadata: None,
memory: None,
chunk_handler: None,
}
}
}
impl AgentRoleBuilder {
pub fn completion_provider(mut self, _provider: impl std::any::Any) -> Self {
self.provider = Some("mistral-small".to_string());
self
}
pub fn temperature(mut self, temp: f64) -> Self {
self.temperature = Some(temp);
self
}
pub fn max_tokens(mut self, max: u64) -> Self {
self.max_tokens = Some(max);
self
}
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
pub fn context(mut self, contexts: impl std::any::Any) -> Self {
self.contexts.push(Box::new(contexts));
self
}
pub fn mcp_server<T>(self) -> McpServerBuilder<T> {
McpServerBuilder {
parent: self,
_phantom: std::marker::PhantomData,
}
}
pub fn tools(mut self, tools: impl std::any::Any) -> Self {
self.tools.push(Box::new(tools));
self
}
pub fn additional_params<T>(mut self, params: T) -> Self
where
T: IntoHashMap,
{
let config_map = params.into_hashmap();
let mut map = HashMap::new();
for (k, v) in config_map {
map.insert(k.to_string(), Value::String(v.to_string()));
}
self.additional_params = Some(map);
self
}
pub fn memory(mut self, memory: Library) -> Self {
self.memory = Some(memory);
self
}
pub fn metadata<T>(mut self, metadata: T) -> Self
where
T: IntoHashMap,
{
let config_map = metadata.into_hashmap();
let mut map = HashMap::new();
for (k, v) in config_map {
map.insert(k.to_string(), Value::String(v.to_string()));
}
self.metadata = Some(map);
self
}
pub fn on_tool_result<F>(self, _handler: F) -> Self
where
F: Fn(Value),
{
self
}
pub fn on_conversation_turn<F>(self, _handler: F) -> Self
where
F: Fn(&str, &Agent) -> String,
{
self
}
pub fn into_agent(self) -> Agent {
Agent {
builder: self,
history: Vec::new(),
}
}
}
impl ChunkHandler<ConversationChunk, String> for AgentRoleBuilder {
fn on_chunk<F>(mut self, handler: F) -> Self
where
F: Fn(Result<ConversationChunk, String>) -> ConversationChunk + Send + Sync + 'static,
{
self.chunk_handler = Some(Box::new(handler));
self
}
}
pub struct McpServerBuilder<T> {
parent: AgentRoleBuilder,
_phantom: std::marker::PhantomData<T>,
}
impl<T> McpServerBuilder<T> {
pub fn bin(self, _path: &str) -> Self {
self
}
pub fn init(self, _cmd: &str) -> AgentRoleBuilder {
self.parent
}
}
impl Agent {
pub fn conversation_history(mut self, role: MessageRole, message: &str) -> Self {
self.history.push((role, message.to_string()));
self
}
pub fn chat(
self,
message: impl Into<String>,
) -> Result<AsyncStream<ConversationChunk>, Box<dyn std::error::Error>> {
let message = message.into();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let chunk = ConversationChunk {
content: format!("Echo: {}", message),
role: MessageRole::Assistant,
error: None,
};
let _ = tx.send(chunk);
Ok(AsyncStream::new(rx))
}
}
pub fn exec_to_text() -> String {
"Command help text".to_string()
}
pub use crate::models::*;