use std::collections::HashMap;
use std::env;
use std::future::Future;
use std::pin::Pin;
use crate::error::{AreevError, Result};
use crate::http::HttpClient;
use crate::types::*;
const DEFAULT_URL: &str = "https://app.areev.ai";
const DEFAULT_MEMORY: &str = "default";
pub struct Areev {
inner: HttpClient,
}
impl Areev {
pub fn from_env() -> Self {
let api_key = env::var("AREEV_API_KEY").ok();
let url = env::var("AREEV_URL").unwrap_or_else(|_| DEFAULT_URL.to_string());
let memory_id = env::var("AREEV_MEMORY_ID").unwrap_or_else(|_| DEFAULT_MEMORY.to_string());
Self {
inner: HttpClient::new(&url, &memory_id, api_key.as_deref()),
}
}
pub fn new(api_key: Option<&str>, url: Option<&str>, memory_id: Option<&str>) -> Self {
Self {
inner: HttpClient::new(
url.unwrap_or(DEFAULT_URL),
memory_id.unwrap_or(DEFAULT_MEMORY),
api_key,
),
}
}
pub async fn remember(&self, text: &str) -> Result<RememberResponse> {
let req = RememberRequest {
text: text.to_string(),
sync: Some(true),
keep_source: None,
namespace: None,
user_id: None,
tags: None,
source_type: None,
created_at: None,
confidence: None,
extract_event_date: None,
auto_relate: None,
};
self.inner.remember(&req).await
}
pub async fn recall(&self, query: &str) -> Result<RecallResponse> {
let req = RecallRequest {
query: Some(query.to_string()),
limit: Some(10),
..Default::default()
};
self.inner.recall(&req).await
}
pub async fn forget(&self, hash: &str) -> Result<()> {
self.inner.forget(hash).await
}
pub async fn add(&self, req: &AddRequest) -> Result<AddResponse> {
self.inner.add(req).await
}
pub async fn get(&self, hash: &str) -> Result<GetResponse> {
self.inner.get(hash).await
}
pub async fn supersede(&self, req: &SupersedeRequest) -> Result<SupersedeResponse> {
self.inner.supersede(req).await
}
pub async fn health(&self) -> Result<HealthResponse> {
self.inner.health().await
}
pub async fn stats(&self) -> Result<StatsResponse> {
self.inner.stats().await
}
pub async fn flush(&self) -> Result<()> {
self.inner.flush().await
}
pub async fn harness_chat(
&self,
slug: &str,
conversation_id: &str,
user_message: &str,
) -> Result<HarnessChatResponse> {
let req = HarnessChatRequest {
conversation_id: conversation_id.to_string(),
user_message: user_message.to_string(),
model: None,
provider: None,
};
self.inner.harness_chat(slug, &req).await
}
pub async fn harness_chat_resume(
&self,
slug: &str,
req: &ChatResumeRequest,
) -> Result<HarnessChatResponse> {
self.inner.harness_chat_resume(slug, req).await
}
pub async fn cancel_harness_chat_session(
&self,
slug: &str,
session_id: &str,
) -> Result<()> {
self.inner
.cancel_harness_chat_session(slug, session_id)
.await
}
pub async fn chat_interactive<E>(
&self,
slug: &str,
conversation_id: &str,
user_message: &str,
executors: &ChatExecutors<E>,
) -> Result<HarnessChatResponse>
where
E: Fn(
String,
serde_json::Value,
)
-> Pin<Box<dyn Future<Output = Result<serde_json::Value>> + Send>>
+ Send
+ Sync,
{
let mut resp = self.harness_chat(slug, conversation_id, user_message).await?;
loop {
if matches!(resp.status, HarnessChatStatus::Completed) {
return Ok(resp);
}
let session_id = resp.session_id.clone().ok_or_else(|| AreevError::Api {
status: 500,
message: "requires_action without session_id".into(),
})?;
let mut tool_outputs = Vec::with_capacity(resp.pending_tool_calls.len());
for pc in &resp.pending_tool_calls {
let executor = executors.get(&pc.tool_name).ok_or_else(|| AreevError::Api {
status: 500,
message: format!(
"no executor registered for tool '{}' (tool_call_id={})",
pc.tool_name, pc.tool_call_id
),
})?;
let args: serde_json::Value =
serde_json::from_str(&pc.arguments).unwrap_or(serde_json::Value::Null);
let output = executor(pc.tool_name.clone(), args).await?;
tool_outputs.push(ChatToolOutput {
tool_call_id: pc.tool_call_id.clone(),
output,
is_error: None,
});
}
let resume_req = ChatResumeRequest {
session_id,
tool_outputs,
tenant_timestamp_ms: None,
};
resp = self.harness_chat_resume(slug, &resume_req).await?;
}
}
}
pub type ChatExecutor = Box<
dyn Fn(
String,
serde_json::Value,
) -> Pin<Box<dyn Future<Output = Result<serde_json::Value>> + Send>>
+ Send
+ Sync,
>;
pub struct ChatExecutors<E = ChatExecutor> {
inner: HashMap<String, E>,
}
impl<E> Default for ChatExecutors<E> {
fn default() -> Self {
Self {
inner: HashMap::new(),
}
}
}
impl<E> ChatExecutors<E> {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, name: impl Into<String>, exec: E) -> &mut Self {
self.inner.insert(name.into(), exec);
self
}
pub fn get(&self, name: &str) -> Option<&E> {
self.inner.get(name)
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}