133 lines
3.6 KiB
Rust
133 lines
3.6 KiB
Rust
use crate::agents::extension::PlatformExtensionContext;
|
|
use crate::agents::mcp_client::{Error, McpClientTrait};
|
|
use anyhow::Result;
|
|
use async_trait::async_trait;
|
|
use rmcp::model::{
|
|
CallToolResult, Content, Implementation, InitializeResult, JsonObject, ListToolsResult,
|
|
ProtocolVersion, ServerCapabilities,
|
|
};
|
|
use tokio::io::AsyncReadExt;
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
pub static EXTENSION_NAME: &str = "tom";
|
|
|
|
const MAX_BYTES: usize = 65_536;
|
|
|
|
pub struct TomClient {
|
|
info: InitializeResult,
|
|
}
|
|
|
|
impl TomClient {
|
|
pub fn new(_context: PlatformExtensionContext) -> Result<Self> {
|
|
Ok(Self {
|
|
info: InitializeResult {
|
|
protocol_version: ProtocolVersion::V_2025_03_26,
|
|
capabilities: ServerCapabilities {
|
|
tools: None,
|
|
tasks: None,
|
|
resources: None,
|
|
prompts: None,
|
|
completions: None,
|
|
experimental: None,
|
|
logging: None,
|
|
},
|
|
server_info: Implementation {
|
|
name: EXTENSION_NAME.to_string(),
|
|
title: Some("Top Of Mind".to_string()),
|
|
version: "1.0.0".to_string(),
|
|
icons: None,
|
|
website_url: None,
|
|
},
|
|
instructions: None,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl McpClientTrait for TomClient {
|
|
async fn list_tools(
|
|
&self,
|
|
_session_id: &str,
|
|
_next_cursor: Option<String>,
|
|
_cancellation_token: CancellationToken,
|
|
) -> Result<ListToolsResult, Error> {
|
|
Ok(ListToolsResult {
|
|
tools: vec![],
|
|
next_cursor: None,
|
|
meta: None,
|
|
})
|
|
}
|
|
|
|
async fn call_tool(
|
|
&self,
|
|
_session_id: &str,
|
|
name: &str,
|
|
_arguments: Option<JsonObject>,
|
|
_working_dir: Option<&str>,
|
|
_cancellation_token: CancellationToken,
|
|
) -> Result<CallToolResult, Error> {
|
|
Ok(CallToolResult::error(vec![Content::text(format!(
|
|
"tom has no tools (called: {name})"
|
|
))]))
|
|
}
|
|
|
|
fn get_info(&self) -> Option<&InitializeResult> {
|
|
Some(&self.info)
|
|
}
|
|
|
|
async fn get_moim(&self, _session_id: &str) -> Option<String> {
|
|
let mut parts = Vec::new();
|
|
|
|
if let Ok(text) = std::env::var("GOOSE_MOIM_MESSAGE_TEXT") {
|
|
if !text.trim().is_empty() {
|
|
parts.push(truncate_utf8(text));
|
|
}
|
|
}
|
|
|
|
if let Ok(path) = std::env::var("GOOSE_MOIM_MESSAGE_FILE") {
|
|
let expanded = shellexpand::tilde(&path);
|
|
if let Some(content) = read_bounded(&expanded).await {
|
|
if !content.trim().is_empty() {
|
|
parts.push(content);
|
|
}
|
|
}
|
|
}
|
|
|
|
if parts.is_empty() {
|
|
None
|
|
} else {
|
|
Some(parts.join("\n"))
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn read_bounded(path: &str) -> Option<String> {
|
|
let mut file = tokio::fs::File::open(path).await.ok()?;
|
|
let mut buf = vec![0u8; MAX_BYTES];
|
|
let mut total = 0;
|
|
loop {
|
|
let n = file.read(&mut buf[total..]).await.ok()?;
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
total += n;
|
|
if total >= MAX_BYTES {
|
|
break;
|
|
}
|
|
}
|
|
buf.truncate(total);
|
|
let s = String::from_utf8_lossy(&buf).into_owned();
|
|
Some(truncate_utf8(s))
|
|
}
|
|
|
|
fn truncate_utf8(s: String) -> String {
|
|
if s.len() <= MAX_BYTES {
|
|
return s;
|
|
}
|
|
s.char_indices()
|
|
.take_while(|(i, c)| i + c.len_utf8() <= MAX_BYTES)
|
|
.map(|(_, c)| c)
|
|
.collect()
|
|
}
|