use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc;
use lingshu_core::agent::{
Agent, AgentBuilder, ConversationResult, IsolatedAgentOptions, SessionSnapshot, StreamEvent,
};
use lingshu_state::{SessionDb, SessionSearchHit, SessionSummary};
use lingshu_tools::registry::ToolRegistry;
use lingshu_types::{Message, Platform};
use crate::config::SdkConfig;
use crate::convert::parse_model_string;
use crate::error::SdkError;
use crate::memory::MemoryManager;
pub struct SdkAgent {
inner: Agent,
}
#[derive(Debug, Clone)]
pub struct SessionExport {
pub snapshot: SessionSnapshot,
pub messages: Vec<Message>,
}
pub struct SdkAgentBuilder {
model: String,
provider_name: String,
max_iterations: Option<u32>,
temperature: Option<f32>,
streaming: Option<bool>,
platform: Option<Platform>,
session_id: Option<String>,
quiet_mode: Option<bool>,
tool_registry: Option<Arc<ToolRegistry>>,
state_db: Option<Arc<SessionDb>>,
enabled_toolsets: Option<Vec<String>>,
disabled_toolsets: Option<Vec<String>>,
disabled_tools: Option<Vec<String>>,
instructions: Option<String>,
skip_context_files: Option<bool>,
skip_memory: Option<bool>,
}
impl SdkAgentBuilder {
fn new(model: &str) -> Result<Self, SdkError> {
let (provider_name, model_name) = parse_model_string(model)?;
Ok(Self {
model: format!("{provider_name}/{model_name}"),
provider_name,
max_iterations: None,
temperature: None,
streaming: None,
platform: None,
session_id: None,
quiet_mode: None,
tool_registry: None,
state_db: None,
enabled_toolsets: None,
disabled_toolsets: None,
disabled_tools: None,
instructions: None,
skip_context_files: None,
skip_memory: None,
})
}
pub fn max_iterations(mut self, n: u32) -> Self {
self.max_iterations = Some(n);
self
}
pub fn temperature(mut self, t: f32) -> Self {
self.temperature = Some(t);
self
}
pub fn streaming(mut self, enabled: bool) -> Self {
self.streaming = Some(enabled);
self
}
pub fn platform(mut self, p: Platform) -> Self {
self.platform = Some(p);
self
}
pub fn session_id(mut self, id: impl Into<String>) -> Self {
self.session_id = Some(id.into());
self
}
pub fn quiet_mode(mut self, enabled: bool) -> Self {
self.quiet_mode = Some(enabled);
self
}
pub fn tools(mut self, registry: Arc<ToolRegistry>) -> Self {
self.tool_registry = Some(registry);
self
}
pub fn state_db(mut self, db: Arc<SessionDb>) -> Self {
self.state_db = Some(db);
self
}
pub fn enabled_toolsets(mut self, toolsets: Vec<String>) -> Self {
self.enabled_toolsets = Some(toolsets);
self
}
pub fn disabled_toolsets(mut self, toolsets: Vec<String>) -> Self {
self.disabled_toolsets = Some(toolsets);
self
}
pub fn disabled_tools(mut self, tools: Vec<String>) -> Self {
self.disabled_tools = Some(tools);
self
}
pub fn instructions(mut self, prompt: impl Into<String>) -> Self {
self.instructions = Some(prompt.into());
self
}
pub fn skip_context_files(mut self, skip: bool) -> Self {
self.skip_context_files = Some(skip);
self
}
pub fn skip_memory(mut self, skip: bool) -> Self {
self.skip_memory = Some(skip);
self
}
pub fn build(self) -> Result<SdkAgent, SdkError> {
let model_name = &self.model[self.provider_name.len() + 1..];
let provider = lingshu_tools::create_provider_for_model(&self.provider_name, model_name)
.map_err(|msg| SdkError::Provider {
model: self.model.clone(),
message: msg,
})?;
let mut builder = AgentBuilder::new(&self.model).provider(provider);
if let Some(n) = self.max_iterations {
builder = builder.max_iterations(n);
}
if let Some(t) = self.temperature {
builder = builder.temperature(t);
}
if let Some(s) = self.streaming {
builder = builder.streaming(s);
}
if let Some(p) = self.platform {
builder = builder.platform(p);
}
if let Some(ref id) = self.session_id {
builder = builder.session_id(id.clone());
}
if let Some(q) = self.quiet_mode {
builder = builder.quiet_mode(q);
}
if let Some(registry) = self.tool_registry {
builder = builder.tools(registry);
}
if let Some(db) = self.state_db {
builder = builder.state_db(db);
}
if let Some(toolsets) = self.enabled_toolsets {
builder = builder.enabled_toolsets(toolsets);
}
if let Some(toolsets) = self.disabled_toolsets {
builder = builder.disabled_toolsets(toolsets);
}
if let Some(tools) = self.disabled_tools {
builder = builder.disabled_tools(tools);
}
if let Some(prompt) = self.instructions {
builder = builder.custom_system_prompt(prompt);
}
if let Some(skip) = self.skip_context_files {
builder = builder.skip_context_files(skip);
}
if let Some(skip) = self.skip_memory {
builder = builder.skip_memory(skip);
}
let agent = builder.build().map_err(SdkError::Agent)?;
Ok(SdkAgent { inner: agent })
}
}
impl SdkAgent {
pub fn new(model: &str) -> Result<Self, SdkError> {
SdkAgentBuilder::new(model)?.build()
}
pub fn builder(model: &str) -> Result<SdkAgentBuilder, SdkError> {
SdkAgentBuilder::new(model)
}
pub fn from_config(config: &SdkConfig) -> Result<Self, SdkError> {
let agent_builder = AgentBuilder::from_config(config.as_inner());
let model_str = &config.as_inner().model.default_model;
let (provider_name, model_name) = parse_model_string(model_str)?;
let provider = lingshu_tools::create_provider_for_model(&provider_name, &model_name)
.map_err(|msg| SdkError::Provider {
model: model_str.clone(),
message: msg,
})?;
let agent = agent_builder
.provider(provider)
.build()
.map_err(SdkError::Agent)?;
Ok(Self { inner: agent })
}
pub async fn chat(&self, message: &str) -> Result<String, SdkError> {
self.inner.chat(message).await.map_err(SdkError::Agent)
}
pub async fn chat_in_cwd(&self, message: &str, cwd: &Path) -> Result<String, SdkError> {
self.inner
.chat_in_cwd(message, cwd)
.await
.map_err(SdkError::Agent)
}
pub async fn stream(
&self,
message: &str,
) -> Result<mpsc::UnboundedReceiver<StreamEvent>, SdkError> {
let (tx, rx) = mpsc::unbounded_channel();
let msg = message.to_string();
let agent = &self.inner;
agent
.chat_streaming(&msg, tx)
.await
.map_err(SdkError::Agent)?;
Ok(rx)
}
pub async fn run(&self, message: &str) -> Result<ConversationResult, SdkError> {
self.inner
.run_conversation(message, None, None)
.await
.map_err(SdkError::Agent)
}
pub async fn run_conversation(
&self,
message: &str,
system: Option<&str>,
history: Option<Vec<Message>>,
) -> Result<ConversationResult, SdkError> {
self.inner
.run_conversation(message, system, history)
.await
.map_err(SdkError::Agent)
}
pub async fn fork(&self) -> Result<Self, SdkError> {
let inner = self
.inner
.fork_isolated(IsolatedAgentOptions::default())
.await
.map_err(SdkError::Agent)?;
Ok(Self { inner })
}
pub fn interrupt(&self) {
self.inner.interrupt();
}
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
pub async fn new_session(&self) {
self.inner.new_session().await;
}
pub async fn session_id(&self) -> Option<String> {
self.inner.session_snapshot().await.session_id
}
pub async fn session_snapshot(&self) -> SessionSnapshot {
self.inner.session_snapshot().await
}
pub async fn messages(&self) -> Vec<Message> {
self.inner.messages().await
}
pub async fn model(&self) -> String {
self.inner.model().await
}
pub async fn list_sessions(&self, limit: usize) -> Result<Vec<SessionSummary>, SdkError> {
self.inner.list_sessions(limit).await.map_err(SdkError::Agent)
}
pub async fn search_sessions(
&self,
query: &str,
limit: usize,
) -> Result<Vec<SessionSearchHit>, SdkError> {
match self.inner.state_db_handle() {
Some(db) => db
.search_sessions_rich(query, limit)
.await
.map_err(SdkError::Agent),
None => Ok(vec![]),
}
}
pub async fn export(&self) -> SessionExport {
let snapshot = self.inner.session_snapshot().await;
let messages = self.inner.messages().await;
SessionExport { snapshot, messages }
}
pub async fn tool_names(&self) -> Vec<String> {
self.inner.tool_names().await
}
pub async fn toolset_summary(&self) -> Vec<(String, usize)> {
self.inner.toolset_summary().await
}
pub async fn set_reasoning_effort(&self, level: Option<String>) {
self.inner.set_reasoning_effort(level).await;
}
pub async fn set_streaming(&self, enabled: bool) {
self.inner.set_streaming(enabled).await;
}
pub async fn compress(&self) {
self.inner.force_compress().await;
}
pub async fn set_model(&self, model: &str) -> Result<(), SdkError> {
let (provider_name, model_name) = crate::convert::parse_model_string(model)?;
let provider = lingshu_tools::create_provider_for_model(&provider_name, &model_name)
.map_err(SdkError::Config)?;
self.inner
.swap_model(format!("{provider_name}/{model_name}"), provider)
.await;
Ok(())
}
pub async fn batch(&self, messages: &[&str]) -> Vec<Result<String, SdkError>> {
let mut handles = Vec::with_capacity(messages.len());
for msg in messages {
let forked = match self.fork().await {
Ok(f) => f,
Err(e) => {
handles.push(tokio::spawn(async move { Err(e) }));
continue;
}
};
let msg = msg.to_string();
handles.push(tokio::spawn(async move { forked.chat(&msg).await }));
}
let mut results = Vec::with_capacity(handles.len());
for handle in handles {
match handle.await {
Ok(result) => results.push(result),
Err(e) => results.push(Err(SdkError::Config(format!("Task panicked: {e}")))),
}
}
results
}
pub fn as_inner(&self) -> &Agent {
&self.inner
}
pub fn memory(&self) -> MemoryManager {
let home = lingshu_core::config::lingshu_home();
MemoryManager::new(home)
}
}