feat: Add tom (Top Of Mind) platform extension (#7073)

This commit is contained in:
tlongwell-block
2026-02-09 11:11:13 -05:00
committed by GitHub
parent 8de69f6e2b
commit 942bcfa4b2
3 changed files with 146 additions and 0 deletions
+13
View File
@@ -4,6 +4,7 @@ use crate::agents::code_execution_extension;
use crate::agents::extension_manager_extension;
use crate::agents::skills_extension;
use crate::agents::todo_extension;
use crate::agents::tom_extension;
use std::collections::HashMap;
use crate::agents::mcp_client::McpClientTrait;
@@ -119,6 +120,18 @@ pub static PLATFORM_EXTENSIONS: Lazy<HashMap<&'static str, PlatformExtensionDef>
},
);
map.insert(
tom_extension::EXTENSION_NAME,
PlatformExtensionDef {
name: tom_extension::EXTENSION_NAME,
display_name: "Top Of Mind",
description:
"Inject custom context into every turn via GOOSE_MOIM_MESSAGE_TEXT and GOOSE_MOIM_MESSAGE_FILE environment variables",
default_enabled: false,
client_factory: |ctx| Box::new(tom_extension::TomClient::new(ctx).unwrap()),
},
);
map
},
);
+1
View File
@@ -24,6 +24,7 @@ pub mod subagent_handler;
mod subagent_task_config;
pub mod subagent_tool;
pub(crate) mod todo_extension;
pub(crate) mod tom_extension;
mod tool_execution;
pub mod types;
+132
View File
@@ -0,0 +1,132 @@
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()
}