
This is just a refactor which adds no functionality. We now return a `ToolResult` from `Tool > run(...)`. For now this just wraps the output task in a struct. We'll use this to implement custom rendering of tools, see #28621. Release Notes: - N/A
126 lines
3.9 KiB
Rust
126 lines
3.9 KiB
Rust
use std::sync::Arc;
|
|
|
|
use anyhow::{Result, anyhow, bail};
|
|
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
|
|
use gpui::{App, Entity, Task};
|
|
use icons::IconName;
|
|
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
|
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 icon(&self) -> IconName {
|
|
IconName::Cog
|
|
}
|
|
|
|
fn source(&self) -> ToolSource {
|
|
ToolSource::ContextServer {
|
|
id: self.server_id.clone().into(),
|
|
}
|
|
}
|
|
|
|
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
|
true
|
|
}
|
|
|
|
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
|
let mut schema = self.tool.input_schema.clone();
|
|
assistant_tool::adapt_schema_to_format(&mut schema, format)?;
|
|
Ok(match 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": [] })
|
|
}
|
|
_ => schema,
|
|
})
|
|
}
|
|
|
|
fn ui_text(&self, _input: &serde_json::Value) -> String {
|
|
format!("Run MCP tool `{}`", self.tool.name)
|
|
}
|
|
|
|
fn run(
|
|
self: Arc<Self>,
|
|
input: serde_json::Value,
|
|
_messages: &[LanguageModelRequestMessage],
|
|
_project: Entity<Project>,
|
|
_action_log: Entity<ActionLog>,
|
|
cx: &mut App,
|
|
) -> ToolResult {
|
|
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
|
let tool_name = self.tool.name.clone();
|
|
let server_clone = server.clone();
|
|
let input_clone = input.clone();
|
|
|
|
cx.spawn(async move |_cx| {
|
|
let Some(protocol) = server_clone.client() else {
|
|
bail!("Context server not initialized");
|
|
};
|
|
|
|
let arguments = if let serde_json::Value::Object(map) = input_clone {
|
|
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)
|
|
})
|
|
.into()
|
|
} else {
|
|
Task::ready(Err(anyhow!("Context server not found"))).into()
|
|
}
|
|
}
|
|
}
|