Dynamically refresh skill instructions each turn (#9217)
Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -56,7 +56,7 @@ impl McpClientTrait for MockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_info(&self) -> std::option::Option<&rmcp::model::InitializeResult> {
|
fn get_info(&self) -> std::option::Option<&rmcp::model::InitializeResult> {
|
||||||
todo!()
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_resource(
|
async fn read_resource(
|
||||||
|
|||||||
@@ -106,9 +106,7 @@ impl Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_instructions(&self) -> Option<String> {
|
fn get_instructions(&self) -> Option<String> {
|
||||||
self.server_info
|
self.client.get_instructions()
|
||||||
.as_ref()
|
|
||||||
.and_then(|info| info.instructions.clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_client(&self) -> McpClientBox {
|
fn get_client(&self) -> McpClientBox {
|
||||||
|
|||||||
@@ -84,6 +84,13 @@ pub trait McpClientTrait: Send + Sync {
|
|||||||
|
|
||||||
fn get_info(&self) -> Option<&InitializeResult>;
|
fn get_info(&self) -> Option<&InitializeResult>;
|
||||||
|
|
||||||
|
/// Return the extension's current instructions. The default reads from
|
||||||
|
/// `get_info()`, but platform extensions can override this to provide
|
||||||
|
/// dynamically computed instructions (e.g. freshly discovered skills).
|
||||||
|
fn get_instructions(&self) -> Option<String> {
|
||||||
|
self.get_info().and_then(|info| info.instructions.clone())
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_resources(
|
async fn list_resources(
|
||||||
&self,
|
&self,
|
||||||
_session_id: &str,
|
_session_id: &str,
|
||||||
|
|||||||
@@ -441,11 +441,9 @@ mod tests {
|
|||||||
.values()
|
.values()
|
||||||
.map(|def| {
|
.map(|def| {
|
||||||
let client = (def.client_factory)(context.clone());
|
let client = (def.client_factory)(context.clone());
|
||||||
let info = client.get_info();
|
let instructions = client.get_instructions().unwrap_or_default();
|
||||||
let instructions = info
|
let has_resources = client
|
||||||
.and_then(|i| i.instructions.clone())
|
.get_info()
|
||||||
.unwrap_or_default();
|
|
||||||
let has_resources = info
|
|
||||||
.and_then(|i| i.capabilities.resources.as_ref())
|
.and_then(|i| i.capabilities.resources.as_ref())
|
||||||
.is_some();
|
.is_some();
|
||||||
ExtensionInfo::new(def.name, &instructions, has_resources)
|
ExtensionInfo::new(def.name, &instructions, has_resources)
|
||||||
|
|||||||
@@ -27,30 +27,8 @@ impl SkillsClient {
|
|||||||
.map(|s| s.working_dir.clone())
|
.map(|s| s.working_dir.clone())
|
||||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
||||||
|
|
||||||
let mut instructions = String::new();
|
|
||||||
if context.session.is_some() {
|
|
||||||
let sources = discover_skills(Some(&working_dir));
|
|
||||||
let mut skills: Vec<&SourceEntry> = sources
|
|
||||||
.iter()
|
|
||||||
.filter(|s| {
|
|
||||||
s.source_type == SourceType::Skill || s.source_type == SourceType::BuiltinSkill
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
skills.sort_by(|a, b| (&a.name, &a.path).cmp(&(&b.name, &b.path)));
|
|
||||||
|
|
||||||
if !skills.is_empty() {
|
|
||||||
instructions.push_str(
|
|
||||||
"\n\nYou have these skills at your disposal, when it is clear they can help you solve a problem or you are asked to use them:",
|
|
||||||
);
|
|
||||||
for skill in &skills {
|
|
||||||
instructions.push_str(&format!("\n• {} - {}", skill.name, skill.description));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
|
let info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
|
||||||
.with_server_info(Implementation::new(EXTENSION_NAME, "1.0.0").with_title("Skills"))
|
.with_server_info(Implementation::new(EXTENSION_NAME, "1.0.0").with_title("Skills"));
|
||||||
.with_instructions(instructions);
|
|
||||||
|
|
||||||
Ok(Self { info, working_dir })
|
Ok(Self { info, working_dir })
|
||||||
}
|
}
|
||||||
@@ -252,6 +230,29 @@ impl McpClientTrait for SkillsClient {
|
|||||||
Some(&self.info)
|
Some(&self.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_instructions(&self) -> Option<String> {
|
||||||
|
let sources = discover_skills(Some(&self.working_dir));
|
||||||
|
let mut skills: Vec<&SourceEntry> = sources
|
||||||
|
.iter()
|
||||||
|
.filter(|s| {
|
||||||
|
s.source_type == SourceType::Skill || s.source_type == SourceType::BuiltinSkill
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
skills.sort_by(|a, b| (&a.name, &a.path).cmp(&(&b.name, &b.path)));
|
||||||
|
|
||||||
|
if skills.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instructions = String::from(
|
||||||
|
"\n\nYou have these skills at your disposal, when it is clear they can help you solve a problem or you are asked to use them:",
|
||||||
|
);
|
||||||
|
for skill in &skills {
|
||||||
|
instructions.push_str(&format!("\n• {} - {}", skill.name, skill.description));
|
||||||
|
}
|
||||||
|
Some(instructions)
|
||||||
|
}
|
||||||
|
|
||||||
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
|
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
|
||||||
let (_tx, rx) = mpsc::channel(1);
|
let (_tx, rx) = mpsc::channel(1);
|
||||||
rx
|
rx
|
||||||
|
|||||||
Reference in New Issue
Block a user