Exposes a new "edit files" tool that the model can use to apply modifications to files in the project. The main model provides instructions and the tool uses a separate "editor" model (Claude 3.5 by default) to generate search/replace blocks like Aider does: ````markdown mathweb/flask/app.py ```python <<<<<<< SEARCH from flask import Flask ======= import math from flask import Flask >>>>>>> REPLACE ``` ```` The search/replace blocks are parsed and applied as they stream in. If a block fails to parse, the tool will apply the other edits and report an error pointing to the part of the input where it occurred. This should allow the model to fix it. Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com>
109 lines
3.4 KiB
Rust
109 lines
3.4 KiB
Rust
use std::sync::Arc;
|
|
|
|
use anyhow::{anyhow, bail, Result};
|
|
use assistant_tool::{Tool, ToolSource};
|
|
use gpui::{App, Entity, Task};
|
|
use language_model::LanguageModelRequestMessage;
|
|
use project::Project;
|
|
|
|
use crate::manager::ContextServerManager;
|
|
use crate::types;
|
|
|
|
pub struct ContextServerTool {
|
|
server_manager: Entity<ContextServerManager>,
|
|
server_id: Arc<str>,
|
|
tool: types::Tool,
|
|
}
|
|
|
|
impl ContextServerTool {
|
|
pub fn new(
|
|
server_manager: Entity<ContextServerManager>,
|
|
server_id: impl Into<Arc<str>>,
|
|
tool: types::Tool,
|
|
) -> Self {
|
|
Self {
|
|
server_manager,
|
|
server_id: server_id.into(),
|
|
tool,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Tool for ContextServerTool {
|
|
fn name(&self) -> String {
|
|
self.tool.name.clone()
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
self.tool.description.clone().unwrap_or_default()
|
|
}
|
|
|
|
fn source(&self) -> ToolSource {
|
|
ToolSource::ContextServer {
|
|
id: self.server_id.clone().into(),
|
|
}
|
|
}
|
|
|
|
fn input_schema(&self) -> serde_json::Value {
|
|
match &self.tool.input_schema {
|
|
serde_json::Value::Null => {
|
|
serde_json::json!({ "type": "object", "properties": [] })
|
|
}
|
|
serde_json::Value::Object(map) if map.is_empty() => {
|
|
serde_json::json!({ "type": "object", "properties": [] })
|
|
}
|
|
_ => self.tool.input_schema.clone(),
|
|
}
|
|
}
|
|
|
|
fn run(
|
|
self: Arc<Self>,
|
|
input: serde_json::Value,
|
|
_messages: &[LanguageModelRequestMessage],
|
|
_project: Entity<Project>,
|
|
cx: &mut App,
|
|
) -> Task<Result<String>> {
|
|
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
|
cx.foreground_executor().spawn({
|
|
let tool_name = self.tool.name.clone();
|
|
async move {
|
|
let Some(protocol) = server.client() else {
|
|
bail!("Context server not initialized");
|
|
};
|
|
|
|
let arguments = if let serde_json::Value::Object(map) = input {
|
|
Some(map.into_iter().collect())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
log::trace!(
|
|
"Running tool: {} with arguments: {:?}",
|
|
tool_name,
|
|
arguments
|
|
);
|
|
let response = protocol.run_tool(tool_name, arguments).await?;
|
|
|
|
let mut result = String::new();
|
|
for content in response.content {
|
|
match content {
|
|
types::ToolResponseContent::Text { text } => {
|
|
result.push_str(&text);
|
|
}
|
|
types::ToolResponseContent::Image { .. } => {
|
|
log::warn!("Ignoring image content from tool response");
|
|
}
|
|
types::ToolResponseContent::Resource { .. } => {
|
|
log::warn!("Ignoring resource content from tool response");
|
|
}
|
|
}
|
|
}
|
|
Ok(result)
|
|
}
|
|
})
|
|
} else {
|
|
Task::ready(Err(anyhow!("Context server not found")))
|
|
}
|
|
}
|
|
}
|