Implement SEP-1577: Sampling With Tools (#628)

* feat: implement SEP-1577 sampling with tools support

* feat: add TryFrom<Content> for backward-compatible migration
This commit is contained in:
Dale Seo 2026-02-06 06:51:26 -05:00 • committed by GitHub
commit 8bd3fcb890
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,14 +13,8 @@ use tokio_util::sync::CancellationToken;
#[tokio::test]
async fn test_message_roles() {
let messages = vec![
SamplingMessage {
role: Role::User,
content: Content::text("user message"),
},
SamplingMessage {
role: Role::Assistant,
content: Content::text("assistant message"),
},
SamplingMessage::user_text("user message"),
SamplingMessage::assistant_text("assistant message"),
];
// Verify all roles can be serialized/deserialized correctly
@ -50,10 +44,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
@ -61,6 +52,8 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -79,7 +72,15 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
text.contains("test context"),
"Response should include context for ThisServer"
@ -94,10 +95,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::AllServers),
model_preferences: None,
system_prompt: None,
@ -105,6 +103,8 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -123,7 +123,15 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
text.contains("test context"),
"Response should include context for AllServers"
@ -138,10 +146,7 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::None),
model_preferences: None,
system_prompt: None,
@ -149,6 +154,8 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -167,7 +174,15 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
!text.contains("test context"),
"Response should not include context for None"
@ -202,10 +217,7 @@ async fn test_context_inclusion_ignored_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
@ -213,6 +225,8 @@ async fn test_context_inclusion_ignored_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -231,7 +245,15 @@ async fn test_context_inclusion_ignored_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
!text.contains("test context"),
"Context should be ignored when client chooses not to honor requests"
@ -266,14 +288,8 @@ async fn test_message_sequence_integration() -> anyhow::Result<()> {
meta: None,
task: None,
messages: vec![
SamplingMessage {
role: Role::User,
content: Content::text("first message"),
},
SamplingMessage {
role: Role::Assistant,
content: Content::text("second message"),
},
SamplingMessage::user_text("first message"),
SamplingMessage::assistant_text("second message"),
],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
@ -282,6 +298,8 @@ async fn test_message_sequence_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -300,7 +318,15 @@ async fn test_message_sequence_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
text.contains("test context"),
"Response should include context when ThisServer is specified"
@ -339,18 +365,9 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
meta: None,
task: None,
messages: vec![
SamplingMessage {
role: Role::User,
content: Content::text("first user message"),
},
SamplingMessage {
role: Role::Assistant,
content: Content::text("first assistant response"),
},
SamplingMessage {
role: Role::User,
content: Content::text("second user message"),
},
SamplingMessage::user_text("first user message"),
SamplingMessage::assistant_text("first assistant response"),
SamplingMessage::user_text("second user message"),
],
include_context: None,
model_preferences: None,
@ -359,6 +376,8 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -384,10 +403,7 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::Assistant,
content: Content::text("assistant message"),
}],
messages: vec![SamplingMessage::assistant_text("assistant message")],
include_context: None,
model_preferences: None,
system_prompt: None,
@ -395,6 +411,8 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -439,10 +457,7 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
@ -450,6 +465,8 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -468,7 +485,15 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
text.contains("test context"),
"ThisServer context request should be honored"
@ -481,10 +506,7 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test message"),
}],
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::AllServers),
model_preferences: None,
system_prompt: None,
@ -492,6 +514,8 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -510,7 +534,15 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(
!text.contains("test context"),
"AllServers context request should be ignored"
@ -540,10 +572,7 @@ async fn test_context_inclusion() -> anyhow::Result<()> {
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage {
role: Role::User,
content: Content::text("test"),
}],
messages: vec![SamplingMessage::user_text("test")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
@ -551,6 +580,8 @@ async fn test_context_inclusion() -> anyhow::Result<()> {
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
@ -569,7 +600,15 @@ async fn test_context_inclusion() -> anyhow::Result<()> {
.await?;
if let ClientResult::CreateMessageResult(result) = result {
let text = result.message.content.as_text().unwrap().text.as_str();
let text = result
.message
.content
.first()
.unwrap()
.as_text()
.unwrap()
.text
.as_str();
assert!(text.contains("test context"));
}