use std::collections::HashMap;
use crate::api::ApiClient;
use crate::conversation::{Conversation, LlmSummarizer, Summarizer};
use crate::raw::request::message::{Message, Role};
use crate::tool_trait::Tool;
use serde_json::Value;
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub struct ToolCallChunk {
pub id: String,
pub name: String,
pub delta: String,
pub index: u32,
}
#[derive(Debug, Clone)]
pub struct ToolCallResult {
pub id: String,
pub name: String,
pub args: String,
pub result: Value,
}
#[derive(Debug, Clone)]
pub enum AgentEvent {
Token(String),
ReasoningToken(String),
ToolCall(ToolCallChunk),
ToolResult(ToolCallResult),
}
pub struct DeepseekAgent {
pub(crate) conversation: Conversation,
pub(crate) tools: Vec<Box<dyn Tool>>,
pub(crate) tool_index: HashMap<String, usize>,
pub(crate) streaming: bool,
pub(crate) model: String,
pub(crate) interrupt_tx: mpsc::UnboundedSender<String>,
pub(crate) interrupt_rx: mpsc::UnboundedReceiver<String>,
pub(crate) tool_inject_tx: mpsc::UnboundedSender<ToolInjection>,
pub(crate) tool_inject_rx: mpsc::UnboundedReceiver<ToolInjection>,
pub(crate) extra_body: Option<serde_json::Map<String, serde_json::Value>>,
}
pub enum ToolInjection {
Add(Box<dyn Tool>),
Remove(Vec<String>),
}
impl DeepseekAgent {
fn from_parts(client: ApiClient, model: impl Into<String>) -> Self {
let model = model.into();
let summarizer = LlmSummarizer::new(client.clone()).with_model(model.clone());
let (interrupt_tx, interrupt_rx) = mpsc::unbounded_channel();
let (tool_inject_tx, tool_inject_rx) = mpsc::unbounded_channel();
Self {
conversation: Conversation::new(client).with_summarizer(summarizer),
tools: vec![],
tool_index: HashMap::new(),
streaming: false,
model,
interrupt_tx,
interrupt_rx,
tool_inject_tx,
tool_inject_rx,
extra_body: None,
}
}
pub fn new(token: impl Into<String>) -> Self {
Self::from_parts(ApiClient::new(token), "deepseek-chat")
}
pub fn custom(
token: impl Into<String>,
base_url: impl Into<String>,
model: impl Into<String>,
) -> Self {
let client = ApiClient::new(token).with_base_url(base_url);
Self::from_parts(client, model)
}
pub fn add_tool<TT: Tool + 'static>(mut self, tool: TT) -> Self {
let idx = self.tools.len();
for raw in tool.raw_tools() {
self.tool_index.insert(raw.function.name.clone(), idx);
}
self.tools.push(Box::new(tool));
self
}
pub fn chat(mut self, user_message: &str) -> crate::agent::stream::AgentStream {
self.conversation.push_user_input(user_message);
crate::agent::stream::AgentStream::new(self)
}
pub fn chat_from_history(self) -> crate::agent::stream::AgentStream {
crate::agent::stream::AgentStream::new(self)
}
pub fn with_streaming(mut self) -> Self {
self.streaming = true;
self
}
pub fn extra_body(mut self, map: serde_json::Map<String, serde_json::Value>) -> Self {
if let Some(ref mut existing) = self.extra_body {
existing.extend(map);
} else {
self.extra_body = Some(map);
}
self
}
pub fn extra_field(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
if let Some(ref mut m) = self.extra_body {
m.insert(key.into(), value);
} else {
let mut m = serde_json::Map::new();
m.insert(key.into(), value);
self.extra_body = Some(m);
}
self
}
pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.conversation
.history_mut()
.insert(0, Message::new(Role::System, &prompt.into()));
self
}
pub fn with_summarizer(mut self, summarizer: impl Summarizer + 'static) -> Self {
self.conversation = self.conversation.with_summarizer(summarizer);
self
}
pub fn with_history(mut self, history: Vec<crate::raw::request::message::Message>) -> Self {
self.conversation = self.conversation.with_history(history);
self
}
pub fn push_user_message_with_name(&mut self, text: &str, name: Option<&str>) {
use crate::raw::request::message::{Message, Role};
let mut msg = Message::new(Role::User, text);
msg.name = name.map(|n| n.to_string());
self.conversation.history_mut().push(msg);
}
pub fn history(&self) -> &[crate::raw::request::message::Message] {
self.conversation.history()
}
pub fn interrupt_sender(&self) -> mpsc::UnboundedSender<String> {
self.interrupt_tx.clone()
}
pub fn tool_inject_sender(&self) -> mpsc::UnboundedSender<ToolInjection> {
self.tool_inject_tx.clone()
}
pub(crate) fn drain_interrupts(&mut self) {
while let Ok(msg) = self.interrupt_rx.try_recv() {
self.conversation
.history_mut()
.push(Message::new(Role::User, &msg));
}
}
pub(crate) fn drain_tool_injections(&mut self) {
while let Ok(injection) = self.tool_inject_rx.try_recv() {
match injection {
ToolInjection::Add(tool) => {
let idx = self.tools.len();
for raw in tool.raw_tools() {
self.tool_index.insert(raw.function.name.clone(), idx);
}
self.tools.push(tool);
}
ToolInjection::Remove(names) => {
let names_set: std::collections::HashSet<&str> =
names.iter().map(String::as_str).collect();
let mut new_tools: Vec<Box<dyn Tool>> = Vec::new();
let mut new_index: HashMap<String, usize> = HashMap::new();
for tool in self.tools.drain(..) {
let raws = tool.raw_tools();
if raws.iter().any(|r| names_set.contains(r.function.name.as_str())) {
continue;
}
let idx = new_tools.len();
for raw in raws {
new_index.insert(raw.function.name.clone(), idx);
}
new_tools.push(tool);
}
self.tools = new_tools;
self.tool_index = new_index;
}
}
}
}
}