use cyrup_sugars::AsyncStream;
use serde_json::Value;
use std::collections::HashMap;
pub trait IntoHashMap {
fn into_hashmap(self) -> hashbrown::HashMap<&'static str, &'static str>;
}
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()
}
}
pub use sugars_collections::hash_map;
macro_rules! array_tuple_method_impl {
($method_name:ident, $field_name:ident) => {
pub fn $method_name<P>(mut self, params: P) -> Self
where
P: FnOnce() -> hashbrown::HashMap<&'static str, &'static str>
{
let config_map = params();
let mut map = HashMap::new();
for (k, v) in config_map {
map.insert(k.to_string(), Value::String(v.to_string()));
}
self.$field_name = Some(map);
self
}
};
}
pub struct JsonClosure<F>(F);
impl<F> JsonClosure<F> {
pub fn new(f: F) -> Self {
JsonClosure(f)
}
}
impl<F> Into<hashbrown::HashMap<&'static str, &'static str>> for JsonClosure<F>
where
F: FnOnce() -> hashbrown::HashMap<&'static str, &'static str>,
{
fn into(self) -> hashbrown::HashMap<&'static str, &'static str> {
(self.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>,
}
#[derive(Debug, Clone, Copy)]
pub enum MessageRole {
User,
System,
Assistant,
}
#[derive(Debug, Clone)]
pub struct MessageChunk {
content: String,
role: MessageRole,
}
impl std::fmt::Display for MessageChunk {
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,
}
}
}
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 on_chunk<F>(self, _handler: F) -> AgentRoleBuilderWithChunkHandler<F>
where
F: Fn(Result<MessageChunk, String>) -> Result<MessageChunk, String> + Send + Sync + 'static,
{
AgentRoleBuilderWithChunkHandler {
inner: self,
chunk_handler: _handler,
}
}
}
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
}
}
pub struct AgentRoleBuilderWithChunkHandler<F> {
#[allow(dead_code)]
inner: AgentRoleBuilder,
#[allow(dead_code)]
chunk_handler: F,
}
impl<F> AgentRoleBuilderWithChunkHandler<F>
where
F: Fn(Result<MessageChunk, String>) -> Result<MessageChunk, String> + Send + Sync + 'static,
{
pub fn into_agent(self) -> Agent {
Agent {
builder: self.inner,
history: Vec::new(),
}
}
}
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<MessageChunk>, Box<dyn std::error::Error>> {
let message = message.into();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let chunk = MessageChunk {
content: format!("Echo: {}", message),
role: MessageRole::Assistant,
};
let _ = tx.send(chunk);
Ok(AsyncStream::new(rx))
}
}
pub fn exec_to_text() -> String {
"Command help text".to_string()
}
pub use crate::models::*;