Skip to content

Comments

ACP editor tools#7388

Open
jamadeo wants to merge 6 commits intomainfrom
jackamadeo/acp-tools
Open

ACP editor tools#7388
jamadeo wants to merge 6 commits intomainfrom
jackamadeo/acp-tools

Conversation

@jamadeo
Copy link
Collaborator

@jamadeo jamadeo commented Feb 20, 2026

This adds an option to goose-acp, --acp-editor-tools, that replaces goose's standard developer tools with ACP based editor tools. These use ACP's file system and terminals methods to call back to the ACP client to perform file edits and shell commands.

This makes for a nicer experience when using goose over ACP in an editor like Zed.

Before:
https://github.com/user-attachments/assets/c3ee6a05-a4ba-411b-a881-02ae909b6274

After:
https://github.com/user-attachments/assets/247730ee-c83e-454f-b71c-b4b07630305a

Copilot AI review requested due to automatic review settings February 20, 2026 15:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an --acp-editor-tools flag to goose-acp that replaces goose's standard developer tools with ACP-based editor tools. These tools use the Agent Client Protocol's file system and terminal methods to delegate file operations and shell commands back to the ACP client (like Zed editor), providing a better integrated experience.

Changes:

  • Introduces ToolCallContext struct to pass session ID, working directory, and tool call request ID through the tool dispatch chain
  • Refactors all call_tool implementations to accept context instead of individual parameters
  • Adds new ACP tools module implementing read, write, str_replace, insert, and shell tools using ACP protocol
  • Refactors text editor functions to separate in-memory logic from file I/O for reusability
  • Updates extension manager to expose tools unprefixed when only one client is active

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
crates/goose/src/agents/tool_execution.rs Adds ToolCallContext struct to encapsulate tool call parameters
crates/goose/src/agents/extension_manager.rs Updates dispatch_tool_call to use ToolCallContext; fixes tool prefixing logic
crates/goose/src/agents/agent.rs Creates ToolCallContext when dispatching tool calls
crates/goose/src/agents/mcp_client.rs Updates McpClientTrait call_tool signature to use ToolCallContext
crates/goose/src/agents/platform_extensions/*.rs Updates all platform extension call_tool implementations
crates/goose-acp/src/tools.rs New module implementing ACP-based file and shell tools
crates/goose-acp/src/server.rs Adds acp_editor_tools flag and conditionally adds ACP tools
crates/goose-acp/src/server_factory.rs Passes acp_editor_tools flag through configuration
crates/goose-mcp/src/developer/text_editor.rs Refactors replace/insert to extract in-memory logic
crates/goose-cli/src/cli.rs Adds CLI flag for acp_editor_tools
crates/goose-server/src/routes/agent.rs Updates tool call dispatch to use ToolCallContext
All test files Updates test code to construct ToolCallContext

Comment on lines +493 to +502
let is_acp_tool = tool_request
.and_then(|req| req.tool_call.as_ref().ok())
// TODO: something more robust here
.is_some_and(|req| {
req.name == "read"
|| req.name == "write"
|| req.name == "str_replace"
|| req.name == "insert"
|| req.name == "shell"
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coding tool names for detection is fragile and creates a maintenance burden. Consider adding a method to AcpTools or a marker to identify these tools programmatically, or store the extension name in the tool metadata and check against "acp-tools" instead.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +74
($t:ty) => {
serde_json::to_value(schema_for!($t))
.unwrap()
.as_object()
.unwrap()
.clone()
};
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema macro uses unwrap() which will panic at initialization if the schema conversion fails. While this is unlikely to fail for these simple parameter types, consider handling the error more gracefully or using a static assertion approach to catch schema issues at compile time rather than at runtime initialization.

Suggested change
($t:ty) => {
serde_json::to_value(schema_for!($t))
.unwrap()
.as_object()
.unwrap()
.clone()
};
($t:ty) => {{
let value = match serde_json::to_value(schema_for!($t)) {
Ok(v) => v,
Err(_) => serde_json::Value::Object(JsonObject::new()),
};
match value.as_object() {
Some(map) => map.clone(),
None => JsonObject::new(),
}
}};

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,400 @@
use std::{cell::LazyCell, fmt::Display, future::Future, path::PathBuf};
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace std::cell::LazyCell with once_cell::sync::Lazy to match the codebase convention. The codebase consistently uses once_cell for lazy static initialization throughout (see usage in goose/src/agents/extension_manager.rs, goose-mcp/src/developer/rmcp_developer.rs, and many other files).

Suggested change
use std::{cell::LazyCell, fmt::Display, future::Future, path::PathBuf};
use std::{fmt::Display, future::Future, path::PathBuf};
use once_cell::sync::Lazy;

Copilot uses AI. Check for mistakes.
Comment on lines +224 to +225
.map_err(|_| {
ServiceError::McpError(ErrorData::internal_error("failed to replace", None))
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error information is being discarded when mapping to internal error. The ErrorData from text_editor_replace_inmem contains useful details (like "'old_str' must appear exactly once") that would help users understand what went wrong. Consider propagating the original error message instead of using a generic "failed to replace" message.

Suggested change
.map_err(|_| {
ServiceError::McpError(ErrorData::internal_error("failed to replace", None))
.map_err(|e| {
ServiceError::McpError(ErrorData::internal_error(
&format!("failed to replace: {e}"),
None,
))

Copilot uses AI. Check for mistakes.
params.position,
&params.new_str.as_str(),
)
.map_err(|_| ServiceError::McpError(ErrorData::internal_error("failed to insert", None)))?;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error information is being discarded when mapping to internal error. The ErrorData from text_editor_insert_inmem contains useful details (like "Insert line X is beyond the end of the file") that would help users understand what went wrong. Consider propagating the original error message instead of using a generic "failed to insert" message.

Suggested change
.map_err(|_| ServiceError::McpError(ErrorData::internal_error("failed to insert", None)))?;
.map_err(ServiceError::McpError)?;

Copilot uses AI. Check for mistakes.
params: WriteParams,
) -> Result<CallToolResult, ServiceError> {
let path = self.working_dir.join(params.path);
let content = read_file(&path, &self.cx, self.session_id.clone()).await?;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The write operation reads the old file content at line 182, but this is unnecessary unless the file already exists. For new files, this will fail. Consider only reading the old content if the file exists, or handle the error case when the file doesn't exist (which is valid for a write operation).

Copilot uses AI. Check for mistakes.
}

fn get_info(&self) -> Option<&InitializeResult> {
todo!()
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unimplemented method will panic if called. Since this implements the McpClientTrait, get_info might be called by the framework. Either implement it with a proper value or return None if this information isn't available for ACP tools.

Suggested change
todo!()
None

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant