use async_trait::async_trait;
use std::sync::Arc;
use crate::core::{CoderLib, CoderLibConfig, CodeRequest};
use crate::lsp::{Position, Range};
use crate::core::error::IntegrationError;
use crate::integration::{
HostIntegration, CoderEvent, MessageLevel, HostInfo, HostCapabilities,
Plugin, PluginContext, HostEvent, HostCommand,
};
use crate::tools::Permission;
pub struct CoderLibPlugin {
coder_lib: Option<CoderLib>,
config: CoderLibConfig,
host_integration: Option<Arc<EditHostIntegration>>,
active_session: Option<String>,
}
impl CoderLibPlugin {
pub fn new(config: CoderLibConfig) -> Self {
Self {
coder_lib: None,
config,
host_integration: None,
active_session: None,
}
}
pub async fn process_ai_request(&mut self, prompt: String) -> Result<(), IntegrationError> {
let coder_lib = self.coder_lib.as_ref()
.ok_or_else(|| IntegrationError::NotAvailable)?;
let session_id = if let Some(session_id) = &self.active_session {
session_id.clone()
} else {
let session_id = coder_lib.create_session(Some("Edit Session".to_string())).await
.map_err(|e| IntegrationError::Communication(e.to_string()))?;
self.active_session = Some(session_id.clone());
session_id
};
let context = if let Some(host) = &self.host_integration {
crate::core::RequestContext {
current_file: host.get_active_file().await.ok().flatten(),
cursor_position: host.get_cursor_position().await.ok(),
selection: host.get_selection().await.ok().flatten(),
project_root: host.get_project_root().await.ok().flatten(),
open_files: host.get_open_files().await.unwrap_or_default(),
}
} else {
crate::core::RequestContext::default()
};
let request = CodeRequest {
session_id,
content: prompt,
attachments: Vec::new(),
model: None,
context,
};
let mut response_stream = coder_lib.process_request(request).await
.map_err(|e| IntegrationError::Communication(e.to_string()))?;
tokio::spawn(async move {
while let Ok(response) = response_stream.recv().await {
if !response.content.is_empty() {
}
if response.is_complete {
break;
}
}
});
Ok(())
}
}
impl Plugin for CoderLibPlugin {
fn name(&self) -> &str {
"CoderLib AI Assistant"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn initialize(&mut self, context: PluginContext) -> Result<(), IntegrationError> {
let mut coder_lib = tokio::runtime::Handle::current().block_on(async {
CoderLib::new(self.config.clone()).await
}).map_err(|e| IntegrationError::Communication(e.to_string()))?;
let host_integration = Arc::new(EditHostIntegration::new(context.host_info));
coder_lib.set_host_integration(host_integration.clone());
self.coder_lib = Some(coder_lib);
self.host_integration = Some(host_integration);
Ok(())
}
fn handle_event(&mut self, event: HostEvent) -> Result<Option<HostCommand>, IntegrationError> {
match event {
HostEvent::KeyPressed(key) if key == "Ctrl+I" => {
Ok(Some(HostCommand::ShowDialog {
title: "AI Assistant".to_string(),
message: "Enter your request:".to_string(),
buttons: vec!["Send".to_string(), "Cancel".to_string()],
}))
}
HostEvent::FileOpened(_path) => {
Ok(None)
}
HostEvent::CursorMoved(_) | HostEvent::SelectionChanged(_) => {
Ok(None)
}
_ => Ok(None),
}
}
fn shutdown(&mut self) -> Result<(), IntegrationError> {
self.coder_lib = None;
self.host_integration = None;
self.active_session = None;
Ok(())
}
}
pub struct EditHostIntegration {
host_info: HostInfo,
}
impl EditHostIntegration {
pub fn new(host_info: HostInfo) -> Self {
Self { host_info }
}
}
#[async_trait]
impl HostIntegration for EditHostIntegration {
async fn on_event(&self, _event: CoderEvent) -> Result<(), IntegrationError> {
Ok(())
}
async fn request_permission(&self, _permission: Permission) -> Result<bool, IntegrationError> {
Ok(true)
}
async fn get_file_content(&self, path: &std::path::Path) -> Result<String, IntegrationError> {
tokio::fs::read_to_string(path).await
.map_err(|e| IntegrationError::Communication(e.to_string()))
}
async fn update_file_content(&self, path: &std::path::Path, content: &str) -> Result<(), IntegrationError> {
tokio::fs::write(path, content).await
.map_err(|e| IntegrationError::Communication(e.to_string()))
}
async fn get_cursor_position(&self) -> Result<Position, IntegrationError> {
Ok(Position { line: 0, character: 0 })
}
async fn set_cursor_position(&self, _position: Position) -> Result<(), IntegrationError> {
Ok(())
}
async fn get_selection(&self) -> Result<Option<Range>, IntegrationError> {
Ok(None)
}
async fn set_selection(&self, _range: Range) -> Result<(), IntegrationError> {
Ok(())
}
async fn insert_text(&self, _text: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn replace_text(&self, _range: Range, _text: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn get_active_file(&self) -> Result<Option<std::path::PathBuf>, IntegrationError> {
Ok(None)
}
async fn get_open_files(&self) -> Result<Vec<std::path::PathBuf>, IntegrationError> {
Ok(Vec::new())
}
async fn get_project_root(&self) -> Result<Option<std::path::PathBuf>, IntegrationError> {
Ok(None)
}
async fn show_message(&self, _message: &str, _level: MessageLevel) -> Result<(), IntegrationError> {
Ok(())
}
async fn show_notification(&self, _title: &str, _message: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn request_input(&self, _prompt: &str, _default: Option<&str>) -> Result<Option<String>, IntegrationError> {
Ok(None)
}
async fn confirm(&self, _message: &str) -> Result<bool, IntegrationError> {
Ok(true)
}
async fn execute_command(&self, _command: &str, _args: &[&str]) -> Result<String, IntegrationError> {
Ok(String::new())
}
fn get_host_info(&self) -> HostInfo {
self.host_info.clone()
}
}
pub fn create_edit_host_info() -> HostInfo {
HostInfo {
name: "Microsoft Edit".to_string(),
version: "1.0.0".to_string(),
capabilities: HostCapabilities {
file_modification: true,
ui_dialogs: true,
command_execution: false,
notifications: true,
project_access: true,
cursor_control: true,
syntax_highlighting: true,
multi_file: true,
},
metadata: serde_json::json!({
"editor_type": "console",
"platform": std::env::consts::OS,
}),
}
}