@@ -1,11 +1,11 @@
|
||||
use crate::mcp_utils::ToolResult;
|
||||
use chrono::Utc;
|
||||
use mcp_core::{ToolCall, ToolResult};
|
||||
use rmcp::model::{
|
||||
AnnotateAble, CallToolRequestParam, Content, ImageContent, JsonObject, PromptMessage,
|
||||
PromptMessageContent, PromptMessageRole, RawContent, RawImageContent, RawTextContent,
|
||||
ResourceContents, Role, TextContent,
|
||||
AnnotateAble, Content, ImageContent, PromptMessage, PromptMessageContent, PromptMessageRole,
|
||||
RawContent, RawImageContent, RawTextContent, ResourceContents, Role, TextContent,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use utoipa::ToSchema;
|
||||
@@ -46,7 +46,7 @@ pub struct ToolRequest {
|
||||
pub id: String,
|
||||
#[serde(with = "tool_result_serde")]
|
||||
#[schema(value_type = Object)]
|
||||
pub tool_call: ToolResult<CallToolRequestParam>,
|
||||
pub tool_call: ToolResult<ToolCall>,
|
||||
}
|
||||
|
||||
impl ToolRequest {
|
||||
@@ -81,7 +81,7 @@ pub struct ToolResponse {
|
||||
pub struct ToolConfirmationRequest {
|
||||
pub id: String,
|
||||
pub tool_name: String,
|
||||
pub arguments: JsonObject,
|
||||
pub arguments: Value,
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ pub struct FrontendToolRequest {
|
||||
pub id: String,
|
||||
#[serde(with = "tool_result_serde")]
|
||||
#[schema(value_type = Object)]
|
||||
pub tool_call: ToolResult<CallToolRequestParam>,
|
||||
pub tool_call: ToolResult<ToolCall>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
||||
@@ -188,10 +188,7 @@ impl MessageContent {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn tool_request<S: Into<String>>(
|
||||
id: S,
|
||||
tool_call: ToolResult<CallToolRequestParam>,
|
||||
) -> Self {
|
||||
pub fn tool_request<S: Into<String>>(id: S, tool_call: ToolResult<ToolCall>) -> Self {
|
||||
MessageContent::ToolRequest(ToolRequest {
|
||||
id: id.into(),
|
||||
tool_call,
|
||||
@@ -208,7 +205,7 @@ impl MessageContent {
|
||||
pub fn tool_confirmation_request<S: Into<String>>(
|
||||
id: S,
|
||||
tool_name: String,
|
||||
arguments: JsonObject,
|
||||
arguments: Value,
|
||||
prompt: Option<String>,
|
||||
) -> Self {
|
||||
MessageContent::ToolConfirmationRequest(ToolConfirmationRequest {
|
||||
@@ -230,10 +227,7 @@ impl MessageContent {
|
||||
MessageContent::RedactedThinking(RedactedThinkingContent { data: data.into() })
|
||||
}
|
||||
|
||||
pub fn frontend_tool_request<S: Into<String>>(
|
||||
id: S,
|
||||
tool_call: ToolResult<CallToolRequestParam>,
|
||||
) -> Self {
|
||||
pub fn frontend_tool_request<S: Into<String>>(id: S, tool_call: ToolResult<ToolCall>) -> Self {
|
||||
MessageContent::FrontendToolRequest(FrontendToolRequest {
|
||||
id: id.into(),
|
||||
tool_call,
|
||||
@@ -563,7 +557,7 @@ impl Message {
|
||||
pub fn with_tool_request<S: Into<String>>(
|
||||
self,
|
||||
id: S,
|
||||
tool_call: ToolResult<CallToolRequestParam>,
|
||||
tool_call: ToolResult<ToolCall>,
|
||||
) -> Self {
|
||||
self.with_content(MessageContent::tool_request(id, tool_call))
|
||||
}
|
||||
@@ -582,7 +576,7 @@ impl Message {
|
||||
self,
|
||||
id: S,
|
||||
tool_name: String,
|
||||
arguments: JsonObject,
|
||||
arguments: Value,
|
||||
prompt: Option<String>,
|
||||
) -> Self {
|
||||
self.with_content(MessageContent::tool_confirmation_request(
|
||||
@@ -593,7 +587,7 @@ impl Message {
|
||||
pub fn with_frontend_tool_request<S: Into<String>>(
|
||||
self,
|
||||
id: S,
|
||||
tool_call: ToolResult<CallToolRequestParam>,
|
||||
tool_call: ToolResult<ToolCall>,
|
||||
) -> Self {
|
||||
self.with_content(MessageContent::frontend_tool_request(id, tool_call))
|
||||
}
|
||||
@@ -734,13 +728,13 @@ impl Message {
|
||||
mod tests {
|
||||
use crate::conversation::message::{Message, MessageContent, MessageMetadata};
|
||||
use crate::conversation::*;
|
||||
use mcp_core::ToolCall;
|
||||
use rmcp::model::{
|
||||
AnnotateAble, CallToolRequestParam, PromptMessage, PromptMessageContent, PromptMessageRole,
|
||||
RawEmbeddedResource, RawImageContent, ResourceContents,
|
||||
AnnotateAble, PromptMessage, PromptMessageContent, PromptMessageRole, RawEmbeddedResource,
|
||||
RawImageContent, ResourceContents,
|
||||
};
|
||||
use rmcp::model::{ErrorCode, ErrorData};
|
||||
use rmcp::object;
|
||||
use serde_json::Value;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_with_text() {
|
||||
@@ -762,10 +756,7 @@ mod tests {
|
||||
.with_text("Hello, I'll help you with that.")
|
||||
.with_tool_request(
|
||||
"tool123",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "test_tool".into(),
|
||||
arguments: Some(object!({"param": "value"})),
|
||||
}),
|
||||
Ok(ToolCall::new("test_tool", json!({"param": "value"}))),
|
||||
);
|
||||
|
||||
let json_str = serde_json::to_string_pretty(&message).unwrap();
|
||||
@@ -865,7 +856,7 @@ mod tests {
|
||||
assert_eq!(req.id, "tool123");
|
||||
if let Ok(tool_call) = &req.tool_call {
|
||||
assert_eq!(tool_call.name, "test_tool");
|
||||
assert_eq!(tool_call.arguments, Some(object!({"param": "value"})))
|
||||
assert_eq!(tool_call.arguments, json!({"param": "value"}));
|
||||
} else {
|
||||
panic!("Expected successful tool call");
|
||||
}
|
||||
@@ -1019,9 +1010,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_message_with_tool_request() {
|
||||
let tool_call = Ok(CallToolRequestParam {
|
||||
name: "test_tool".into(),
|
||||
arguments: Some(object!({})),
|
||||
let tool_call = Ok(ToolCall {
|
||||
name: "test_tool".to_string(),
|
||||
arguments: serde_json::json!({}),
|
||||
});
|
||||
|
||||
let message = Message::assistant().with_tool_request("req1", tool_call);
|
||||
|
||||
@@ -380,8 +380,9 @@ pub fn debug_conversation_fix(
|
||||
mod tests {
|
||||
use crate::conversation::message::Message;
|
||||
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
|
||||
use rmcp::model::{CallToolRequestParam, Role};
|
||||
use rmcp::object;
|
||||
use mcp_core::tool::ToolCall;
|
||||
use rmcp::model::Role;
|
||||
use serde_json::json;
|
||||
|
||||
fn run_verify(messages: Vec<Message>) -> (Vec<Message>, Vec<String>) {
|
||||
let (fixed, issues) = fix_conversation(Conversation::new_unvalidated(messages.clone()));
|
||||
@@ -409,10 +410,10 @@ mod tests {
|
||||
.with_text("I'll help you search.")
|
||||
.with_tool_request(
|
||||
"search_1",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "web_search".into(),
|
||||
arguments: Some(object!({"query": "rust programming"})),
|
||||
}),
|
||||
Ok(ToolCall::new(
|
||||
"web_search",
|
||||
json!({"query": "rust programming"}),
|
||||
)),
|
||||
),
|
||||
Message::user().with_tool_response("search_1", Ok(vec![])),
|
||||
Message::assistant().with_text("Based on the search results, here's what I found..."),
|
||||
@@ -454,13 +455,7 @@ mod tests {
|
||||
.with_tool_response("orphan_1", Ok(vec![])), // Wrong role
|
||||
Message::assistant().with_thinking("Let me think", "sig"),
|
||||
Message::user()
|
||||
.with_tool_request(
|
||||
"bad_req",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "search".into(),
|
||||
arguments: Some(object!({})),
|
||||
}),
|
||||
)
|
||||
.with_tool_request("bad_req", Ok(ToolCall::new("search", json!({}))))
|
||||
.with_text("User with bad tool request"),
|
||||
];
|
||||
|
||||
@@ -495,22 +490,11 @@ mod tests {
|
||||
let messages = vec![
|
||||
Message::assistant()
|
||||
.with_text("I'll search for you")
|
||||
.with_tool_request(
|
||||
"search_1",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "search".into(),
|
||||
arguments: Some(object!({})),
|
||||
}),
|
||||
),
|
||||
.with_tool_request("search_1", Ok(ToolCall::new("search", json!({})))),
|
||||
Message::user(),
|
||||
Message::user().with_tool_response("wrong_id", Ok(vec![])),
|
||||
Message::assistant().with_tool_request(
|
||||
"search_2",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "search".into(),
|
||||
arguments: Some(object!({})),
|
||||
}),
|
||||
),
|
||||
Message::assistant()
|
||||
.with_tool_request("search_2", Ok(ToolCall::new("search", json!({})))),
|
||||
];
|
||||
|
||||
let (fixed, issues) = run_verify(messages);
|
||||
@@ -530,18 +514,14 @@ mod tests {
|
||||
fn test_real_world_consecutive_assistant_messages() {
|
||||
let conversation = Conversation::new_unvalidated(vec![
|
||||
Message::user().with_text("run ls in the current directory and then run a word count on the smallest file"),
|
||||
|
||||
Message::assistant()
|
||||
.with_text("I'll help you run `ls` in the current directory and then perform a word count on the smallest file. Let me start by listing the directory contents.")
|
||||
.with_tool_request("toolu_bdrk_018adWbP4X26CfoJU5hkhu3i", Ok(CallToolRequestParam { name: "developer__shell".into(), arguments: Some(object!({"command": "ls -la"})) })),
|
||||
|
||||
.with_tool_request("toolu_bdrk_018adWbP4X26CfoJU5hkhu3i", Ok(ToolCall::new("developer__shell", json!({"command": "ls -la"})))),
|
||||
Message::assistant()
|
||||
.with_text("Now I'll identify the smallest file by size. Looking at the output, I can see that both `slack.yaml` and `subrecipes.yaml` have a size of 0 bytes, making them the smallest files. I'll run a word count on one of them:")
|
||||
.with_tool_request("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(CallToolRequestParam { name: "developer__shell".into(), arguments: Some(object!({"command": "wc slack.yaml"})) })),
|
||||
|
||||
.with_tool_request("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(ToolCall::new("developer__shell", json!({"command": "wc slack.yaml"})))),
|
||||
Message::user()
|
||||
.with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(vec![])),
|
||||
|
||||
Message::assistant()
|
||||
.with_text("I ran `ls -la` in the current directory and found several files. Looking at the file sizes, I can see that both `slack.yaml` and `subrecipes.yaml` are 0 bytes (the smallest files). I ran a word count on `slack.yaml` which shows: **0 lines**, **0 words**, **0 characters**"),
|
||||
Message::user().with_text("thanks!"),
|
||||
@@ -561,13 +541,7 @@ mod tests {
|
||||
Message::user().with_text("Search for something"),
|
||||
Message::assistant()
|
||||
.with_text("I'll search for you")
|
||||
.with_tool_request(
|
||||
"search_1",
|
||||
Ok(CallToolRequestParam {
|
||||
name: "search".into(),
|
||||
arguments: Some(object!({})),
|
||||
}),
|
||||
),
|
||||
.with_tool_request("search_1", Ok(ToolCall::new("search", json!({})))),
|
||||
Message::user().with_tool_response("search_1", Ok(vec![])),
|
||||
Message::user().with_text("Thanks!"),
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::mcp_utils::ToolResult;
|
||||
use mcp_core::ToolResult;
|
||||
use rmcp::model::{ErrorCode, ErrorData};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
Reference in New Issue
Block a user