WIP
This commit is contained in:
parent
27877325bc
commit
5d621bef78
3 changed files with 87 additions and 133 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -152,6 +152,7 @@ dependencies = [
|
|||
name = "agent2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"acp_thread",
|
||||
"agent-client-protocol",
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
|
|
|
@ -12,6 +12,7 @@ path = "src/agent2.rs"
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
acp_thread.workspace = true
|
||||
agent-client-protocol.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
|
|
|
@ -1,173 +1,125 @@
|
|||
//! Agent implementation for the agent-client-protocol
|
||||
//!
|
||||
//! Implementation Status:
|
||||
//! - [x] initialize: Complete - Basic protocol handshake
|
||||
//! - [x] authenticate: Complete - Accepts any auth (stub)
|
||||
//! - [~] new_session: Partial - Creates session ID but Thread creation needs GPUI context
|
||||
//! - [~] load_session: Stub - Returns not implemented
|
||||
//! - [ ] prompt: Stub - Needs GPUI context and type conversions
|
||||
//! - [~] cancelled: Partial - Removes session from map but needs GPUI cleanup
|
||||
|
||||
use agent_client_protocol as acp;
|
||||
use gpui::Entity;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use anyhow::Result;
|
||||
use gpui::{App, AppContext, AsyncApp, Entity, Task};
|
||||
use project::Project;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{templates::Templates, Thread};
|
||||
|
||||
pub struct Agent {
|
||||
/// Session ID -> Thread entity mapping
|
||||
sessions: RefCell<HashMap<acp::SessionId, Entity<Thread>>>,
|
||||
sessions: HashMap<acp::SessionId, Entity<Thread>>,
|
||||
/// Shared templates for all threads
|
||||
templates: Arc<Templates>,
|
||||
/// Current protocol version we support
|
||||
protocol_version: acp::ProtocolVersion,
|
||||
/// Authentication state
|
||||
authenticated: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
pub fn new(templates: Arc<Templates>) -> Self {
|
||||
Self {
|
||||
sessions: RefCell::new(HashMap::new()),
|
||||
sessions: HashMap::new(),
|
||||
templates,
|
||||
protocol_version: acp::VERSION,
|
||||
authenticated: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl acp::Agent for Agent {
|
||||
/// COMPLETE: Initialize handshake with client
|
||||
async fn initialize(
|
||||
&self,
|
||||
arguments: acp::InitializeRequest,
|
||||
) -> Result<acp::InitializeResponse, acp::Error> {
|
||||
// For now, we just use the client's requested version
|
||||
let response_version = arguments.protocol_version.clone();
|
||||
/// Wrapper struct that implements the AgentConnection trait
|
||||
pub struct AgentConnection(pub Entity<Agent>);
|
||||
|
||||
Ok(acp::InitializeResponse {
|
||||
protocol_version: response_version,
|
||||
agent_capabilities: acp::AgentCapabilities::default(),
|
||||
auth_methods: vec![
|
||||
// STUB: No authentication required for now
|
||||
acp::AuthMethod {
|
||||
id: acp::AuthMethodId("none".into()),
|
||||
label: "No Authentication".to_string(),
|
||||
description: Some("No authentication required".to_string()),
|
||||
},
|
||||
],
|
||||
impl acp_thread::AgentConnection for AgentConnection {
|
||||
fn new_thread(
|
||||
self: Rc<Self>,
|
||||
project: Entity<Project>,
|
||||
cwd: &Path,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<Entity<acp_thread::AcpThread>>> {
|
||||
let _cwd = cwd.to_owned();
|
||||
let agent = self.0.clone();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
// Create Thread and store in Agent
|
||||
let (session_id, _thread) =
|
||||
agent.update(cx, |agent, cx: &mut gpui::Context<Agent>| {
|
||||
let thread = cx.new(|_| Thread::new(agent.templates.clone()));
|
||||
let session_id = acp::SessionId(uuid::Uuid::new_v4().to_string().into());
|
||||
agent.sessions.insert(session_id.clone(), thread.clone());
|
||||
(session_id, thread)
|
||||
})?;
|
||||
|
||||
// Create AcpThread
|
||||
let acp_thread = cx.update(|cx| {
|
||||
cx.new(|cx| acp_thread::AcpThread::new("agent2", self, project, session_id, cx))
|
||||
})?;
|
||||
|
||||
Ok(acp_thread)
|
||||
})
|
||||
}
|
||||
|
||||
/// COMPLETE: Handle authentication (currently just accepts any auth)
|
||||
async fn authenticate(&self, _arguments: acp::AuthenticateRequest) -> Result<(), acp::Error> {
|
||||
// STUB: Accept any authentication method for now
|
||||
self.authenticated.set(true);
|
||||
Ok(())
|
||||
fn auth_methods(&self) -> &[acp::AuthMethod] {
|
||||
&[] // No auth for in-process
|
||||
}
|
||||
|
||||
/// PARTIAL: Create a new session
|
||||
async fn new_session(
|
||||
&self,
|
||||
arguments: acp::NewSessionRequest,
|
||||
) -> Result<acp::NewSessionResponse, acp::Error> {
|
||||
// Check if authenticated
|
||||
if !self.authenticated.get() {
|
||||
return Ok(acp::NewSessionResponse { session_id: None });
|
||||
}
|
||||
|
||||
// STUB: Generate a simple session ID
|
||||
let session_id = acp::SessionId(format!("session-{}", uuid::Uuid::new_v4()).into());
|
||||
|
||||
// Create a new Thread for this session
|
||||
// TODO: This needs to be done on the main thread with proper GPUI context
|
||||
// For now, we'll return the session ID and expect the actual Thread creation
|
||||
// to happen when we have access to a GPUI context
|
||||
|
||||
// STUB: MCP server support not implemented
|
||||
if !arguments.mcp_servers.is_empty() {
|
||||
log::warn!("MCP servers requested but not yet supported");
|
||||
}
|
||||
|
||||
Ok(acp::NewSessionResponse {
|
||||
session_id: Some(session_id),
|
||||
})
|
||||
fn authenticate(&self, _method: acp::AuthMethodId, _cx: &mut App) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
/// STUB: Load existing session
|
||||
async fn load_session(
|
||||
&self,
|
||||
_arguments: acp::LoadSessionRequest,
|
||||
) -> Result<acp::LoadSessionResponse, acp::Error> {
|
||||
// STUB: Session persistence not implemented
|
||||
Ok(acp::LoadSessionResponse {
|
||||
auth_required: !self.authenticated.get(),
|
||||
auth_methods: if self.authenticated.get() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![acp::AuthMethod {
|
||||
id: acp::AuthMethodId("none".into()),
|
||||
label: "No Authentication".to_string(),
|
||||
description: Some("No authentication required".to_string()),
|
||||
}]
|
||||
},
|
||||
})
|
||||
}
|
||||
fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>> {
|
||||
let session_id = params.session_id.clone();
|
||||
let agent = self.0.clone();
|
||||
|
||||
/// STUB: Handle prompts
|
||||
async fn prompt(&self, arguments: acp::PromptRequest) -> Result<(), acp::Error> {
|
||||
// TODO: This needs to be implemented with proper GPUI context access
|
||||
// The implementation would:
|
||||
// 1. Look up the Thread for this session
|
||||
// 2. Convert acp::ContentBlock to agent2 message format
|
||||
// 3. Call thread.send() with the converted message
|
||||
// 4. Stream responses back to the client
|
||||
cx.spawn(|cx| async move {
|
||||
// Get thread
|
||||
let thread: Entity<Thread> = agent
|
||||
.read_with(cx, |agent, _| agent.sessions.get(&session_id).cloned())?
|
||||
.ok_or_else(|| anyhow::anyhow!("Session not found"))?;
|
||||
|
||||
let _session_id = arguments.session_id;
|
||||
let _prompt = arguments.prompt;
|
||||
// Convert prompt to message
|
||||
let message = convert_prompt_to_message(params.prompt);
|
||||
|
||||
// STUB: Just acknowledge receipt for now
|
||||
log::info!("Received prompt for session: {}", _session_id.0);
|
||||
// TODO: Get model from somewhere - for now use a placeholder
|
||||
log::warn!("Model selection not implemented - need to get from UI context");
|
||||
|
||||
Err(acp::Error::internal_error().with_data("Prompt handling not yet implemented"))
|
||||
}
|
||||
// Send to thread
|
||||
// thread.update(&mut cx, |thread, cx| {
|
||||
// thread.send(model, message, cx)
|
||||
// })?;
|
||||
|
||||
/// PARTIAL: Handle cancellation
|
||||
async fn cancelled(&self, args: acp::CancelledNotification) -> Result<(), acp::Error> {
|
||||
// Remove the session from our map
|
||||
let removed = self.sessions.borrow_mut().remove(&args.session_id);
|
||||
|
||||
if removed.is_some() {
|
||||
// TODO: Properly clean up the Thread entity when we have GPUI context
|
||||
log::info!("Session {} cancelled and removed", args.session_id.0);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(acp::Error::invalid_request()
|
||||
.with_data(format!("Session {} not found", args.session_id.0)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn cancel(&self, session_id: &acp::SessionId, cx: &mut App) {
|
||||
self.0.update(cx, |agent, _cx| {
|
||||
agent.sessions.remove(session_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for type conversions between acp and agent2 types
|
||||
/// Convert ACP content blocks to a message string
|
||||
fn convert_prompt_to_message(blocks: Vec<acp::ContentBlock>) -> String {
|
||||
let mut message = String::new();
|
||||
|
||||
/// Convert acp::ContentBlock to agent2 message format
|
||||
/// STUB: Needs implementation
|
||||
fn convert_content_block(_block: acp::ContentBlock) -> String {
|
||||
// TODO: Implement proper conversion
|
||||
// This would handle:
|
||||
// - Text content
|
||||
// - Resource links
|
||||
// - Images
|
||||
// - Audio
|
||||
// - Other content types
|
||||
"".to_string()
|
||||
}
|
||||
for block in blocks {
|
||||
match block {
|
||||
acp::ContentBlock::Text(text) => {
|
||||
message.push_str(&text.text);
|
||||
}
|
||||
acp::ContentBlock::ResourceLink(link) => {
|
||||
message.push_str(&format!(" @{} ", link.uri));
|
||||
}
|
||||
acp::ContentBlock::Image(_) => {
|
||||
message.push_str(" [image] ");
|
||||
}
|
||||
acp::ContentBlock::Audio(_) => {
|
||||
message.push_str(" [audio] ");
|
||||
}
|
||||
acp::ContentBlock::Resource(resource) => {
|
||||
message.push_str(&format!(" [resource: {:?}] ", resource.resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert agent2 messages to acp format for responses
|
||||
/// STUB: Needs implementation
|
||||
fn convert_to_acp_content(_content: &str) -> Vec<acp::ContentBlock> {
|
||||
// TODO: Implement proper conversion
|
||||
vec![]
|
||||
message
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue