pub mod agent;
pub mod session;
pub mod config;
pub mod error;
use std::sync::Arc;
use tokio::sync::broadcast;
use uuid::Uuid;
pub use agent::Agent;
pub use config::CoderLibConfig;
pub use error::CoderLibError;
pub use session::{Session, SessionManager};
use crate::integration::HostIntegration;
use crate::lsp::{
LspManager, LspService, LspDiagnostic, Position, Range, Location,
CompletionItem, Hover, CodeAction, SymbolInformation
};
pub struct CoderLib {
agent: Agent,
session_manager: SessionManager,
config: CoderLibConfig,
lsp_manager: Option<LspManager>,
host_integration: Option<Arc<dyn HostIntegration>>,
}
#[derive(Debug, Clone)]
pub struct CodeRequest {
pub session_id: String,
pub content: String,
pub attachments: Vec<Attachment>,
pub model: Option<String>,
pub context: RequestContext,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Attachment {
pub path: std::path::PathBuf,
pub mime_type: String,
pub content: Vec<u8>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct RequestContext {
pub current_file: Option<std::path::PathBuf>,
pub cursor_position: Option<Position>,
pub selection: Option<Range>,
pub project_root: Option<std::path::PathBuf>,
pub open_files: Vec<std::path::PathBuf>,
}
#[derive(Debug, Clone)]
pub struct CodeResponse {
pub session_id: String,
pub content: String,
pub is_complete: bool,
pub tool_calls: Vec<ToolCall>,
pub token_usage: Option<TokenUsage>,
}
#[derive(Debug, Clone)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub parameters: serde_json::Value,
pub result: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct TokenUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
pub cache_creation_tokens: u32,
pub cache_read_tokens: u32,
}
impl TokenUsage {
pub fn new(input_tokens: u32, output_tokens: u32) -> Self {
let total_tokens = input_tokens + output_tokens;
Self {
input_tokens,
output_tokens,
total_tokens,
cache_creation_tokens: 0,
cache_read_tokens: 0,
}
}
pub fn total(&self) -> u32 {
self.input_tokens + self.output_tokens + self.cache_creation_tokens + self.cache_read_tokens
}
pub fn update_total(&mut self) {
self.total_tokens = self.input_tokens + self.output_tokens + self.cache_creation_tokens + self.cache_read_tokens;
}
}
impl Default for CodeRequest {
fn default() -> Self {
Self {
session_id: Uuid::new_v4().to_string(),
content: String::new(),
attachments: Vec::new(),
model: None,
context: RequestContext::default(),
}
}
}
impl CoderLib {
pub async fn new(config: CoderLibConfig) -> Result<Self, CoderLibError> {
let storage = config.create_storage().await?;
let session_manager = SessionManager::new(storage.clone());
let agent = Agent::new(config.clone(), storage).await?;
let lsp_manager = if config.lsp.enabled {
let mut manager = LspManager::new(config.lsp.clone());
let workspace_root = std::env::current_dir().ok();
if let Err(e) = manager.initialize(workspace_root).await {
tracing::warn!("Failed to initialize LSP manager: {}", e);
None
} else {
Some(manager)
}
} else {
None
};
Ok(Self {
agent,
session_manager,
config,
lsp_manager,
host_integration: None,
})
}
pub fn set_host_integration(&mut self, host: Arc<dyn HostIntegration>) {
self.host_integration = Some(host.clone());
self.agent.set_host_integration(host);
}
pub async fn process_request(
&self,
request: CodeRequest,
) -> Result<broadcast::Receiver<CodeResponse>, CoderLibError> {
self.agent.process_request(request).await
}
pub async fn create_session(&self, title: Option<String>) -> Result<String, CoderLibError> {
self.session_manager.create_session(title).await.map_err(CoderLibError::Session)
}
pub async fn get_session(&self, session_id: &str) -> Result<Option<Session>, CoderLibError> {
self.session_manager.get_session(session_id).await.map_err(CoderLibError::Session)
}
pub async fn list_sessions(&self) -> Result<Vec<Session>, CoderLibError> {
self.session_manager.list_sessions().await.map_err(CoderLibError::Session)
}
pub async fn cancel_request(&self, session_id: &str) -> Result<(), CoderLibError> {
self.agent.cancel_request(session_id).await
}
pub fn available_models(&self) -> Vec<crate::llm::Model> {
self.agent.available_models()
}
pub async fn update_config(&mut self, config: CoderLibConfig) -> Result<(), CoderLibError> {
self.config = config.clone();
self.agent.update_config(config).await
}
pub async fn get_diagnostics(&self, file_path: &std::path::Path) -> Result<Vec<LspDiagnostic>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.get_diagnostics(file_path).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_did_open(&self, file_path: &std::path::Path, content: &str) -> Result<(), CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.did_open_file(file_path, content).await.map_err(CoderLibError::Lsp)
} else {
Ok(())
}
}
pub async fn lsp_did_change(&self, file_path: &std::path::Path, content: &str) -> Result<(), CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.did_change_file(file_path, content).await.map_err(CoderLibError::Lsp)
} else {
Ok(())
}
}
pub async fn lsp_did_close(&self, file_path: &std::path::Path) -> Result<(), CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.did_close_file(file_path).await.map_err(CoderLibError::Lsp)
} else {
Ok(())
}
}
pub fn lsp_supports_file(&self, file_path: &std::path::Path) -> bool {
if let Some(lsp) = &self.lsp_manager {
lsp.supports_file(file_path)
} else {
false
}
}
pub async fn lsp_active_servers(&self) -> Vec<String> {
if let Some(lsp) = &self.lsp_manager {
lsp.active_servers().await
} else {
Vec::new()
}
}
pub async fn lsp_get_completions(&self, file_path: &std::path::Path, position: Position) -> Result<Vec<CompletionItem>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.get_completions(file_path, position).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_goto_definition(&self, file_path: &std::path::Path, position: Position) -> Result<Vec<Location>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.goto_definition(file_path, position).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_get_hover(&self, file_path: &std::path::Path, position: Position) -> Result<Option<Hover>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.get_hover(file_path, position).await.map_err(CoderLibError::Lsp)
} else {
Ok(None)
}
}
pub async fn lsp_find_references(&self, file_path: &std::path::Path, position: Position, include_declaration: bool) -> Result<Vec<Location>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.find_references(file_path, position, include_declaration).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_get_code_actions(&self, file_path: &std::path::Path, range: Range) -> Result<Vec<CodeAction>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.get_code_actions(file_path, range).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_get_document_symbols(&self, file_path: &std::path::Path) -> Result<Vec<SymbolInformation>, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.get_document_symbols(file_path).await.map_err(CoderLibError::Lsp)
} else {
Ok(Vec::new())
}
}
pub async fn lsp_format_document(&self, file_path: &std::path::Path) -> Result<String, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.format_document(file_path).await.map_err(CoderLibError::Lsp)
} else {
Err(CoderLibError::Lsp(crate::lsp::LspError::ServerNotFound("LSP not available".to_string())))
}
}
pub async fn lsp_format_range(&self, file_path: &std::path::Path, range: Range) -> Result<String, CoderLibError> {
if let Some(lsp) = &self.lsp_manager {
lsp.format_range(file_path, range).await.map_err(CoderLibError::Lsp)
} else {
Err(CoderLibError::Lsp(crate::lsp::LspError::ServerNotFound("LSP not available".to_string())))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_creation() {
let pos = Position { line: 10, character: 5 };
assert_eq!(pos.line, 10);
assert_eq!(pos.character, 5);
}
#[test]
fn test_range_creation() {
let range = Range {
start: Position { line: 1, character: 0 },
end: Position { line: 1, character: 10 },
};
assert_eq!(range.start.line, 1);
assert_eq!(range.end.character, 10);
}
#[test]
fn test_code_request_default() {
let request = CodeRequest::default();
assert!(!request.session_id.is_empty());
assert!(request.content.is_empty());
assert!(request.attachments.is_empty());
}
}