feat: upgrade rmcp (#3738)

This commit is contained in:
Andrew Harvard
2025-07-31 08:57:51 -04:00
committed by GitHub
parent 86871ad855
commit 001bcebcee
6 changed files with 392 additions and 281 deletions
Generated
+49 -13
View File
@@ -1981,8 +1981,18 @@ version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.20.10",
"darling_macro", "darling_macro 0.20.10",
]
[[package]]
name = "darling"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a79c4acb1fd5fa3d9304be4c76e031c54d2e92d172a393e24b19a14fe8532fe9"
dependencies = [
"darling_core 0.21.0",
"darling_macro 0.21.0",
] ]
[[package]] [[package]]
@@ -1999,13 +2009,38 @@ dependencies = [
"syn 2.0.99", "syn 2.0.99",
] ]
[[package]]
name = "darling_core"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74875de90daf30eb59609910b84d4d368103aaec4c924824c6799b28f77d6a1d"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.99",
]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.20.10" version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.20.10",
"quote",
"syn 2.0.99",
]
[[package]]
name = "darling_macro"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79f8e61677d5df9167cd85265f8e5f64b215cdea3fb55eebc3e622e44c7a146"
dependencies = [
"darling_core 0.21.0",
"quote", "quote",
"syn 2.0.99", "syn 2.0.99",
] ]
@@ -6761,9 +6796,9 @@ dependencies = [
[[package]] [[package]]
name = "rmcp" name = "rmcp"
version = "0.2.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37f2048a81a7ff7e8ef6bc5abced70c3d9114c8f03d85d7aaaafd9fd04f12e9e" checksum = "824daba0a34f8c5c5392295d381e0800f88fd986ba291699f8785f05fa344c1e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"chrono", "chrono",
@@ -6782,11 +6817,11 @@ dependencies = [
[[package]] [[package]]
name = "rmcp-macros" name = "rmcp-macros"
version = "0.2.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72398e694b9f6dbb5de960cf158c8699e6a1854cb5bbaac7de0646b2005763c4" checksum = "ad6543c0572a4dbc125c23e6f54963ea9ba002294fd81dd4012c204219b0dcaa"
dependencies = [ dependencies = [
"darling", "darling 0.21.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_json", "serde_json",
@@ -7072,12 +7107,13 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
dependencies = [ dependencies = [
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"ref-cast",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@@ -7085,9 +7121,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.22" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -7278,7 +7314,7 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [ dependencies = [
"darling", "darling 0.20.10",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.99",
+1 -1
View File
@@ -14,7 +14,7 @@ description = "An AI agent"
uninlined_format_args = "allow" uninlined_format_args = "allow"
[workspace.dependencies] [workspace.dependencies]
rmcp = { version = "0.2.1", features = ["schemars"] } rmcp = { version = "0.3.1", features = ["schemars"] }
# Patch for Windows cross-compilation issue with crunchy # Patch for Windows cross-compilation issue with crunchy
[patch.crates-io] [patch.crates-io]
+219 -155
View File
@@ -12,12 +12,11 @@ use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata};
use goose::session::info::SessionInfo; use goose::session::info::SessionInfo;
use goose::session::SessionMetadata; use goose::session::SessionMetadata;
use rmcp::model::{ use rmcp::model::{
Annotations, Content, EmbeddedResource, ImageContent, ResourceContents, Role, TextContent, Annotations, Content, EmbeddedResource, ImageContent, RawEmbeddedResource, RawImageContent,
Tool, ToolAnnotations, RawTextContent, ResourceContents, Role, TextContent, Tool, ToolAnnotations,
}; };
use utoipa::{OpenApi, ToSchema}; use utoipa::{OpenApi, ToSchema};
use rmcp::schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
use utoipa::openapi::schema::{ use utoipa::openapi::schema::{
AdditionalProperties, AnyOfBuilder, ArrayBuilder, ObjectBuilder, OneOfBuilder, Schema, AdditionalProperties, AnyOfBuilder, ArrayBuilder, ObjectBuilder, OneOfBuilder, Schema,
SchemaFormat, SchemaType, SchemaFormat, SchemaType,
@@ -30,7 +29,7 @@ macro_rules! derive_utoipa {
impl<'__s> ToSchema<'__s> for $schema_name { impl<'__s> ToSchema<'__s> for $schema_name {
fn schema() -> (&'__s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) { fn schema() -> (&'__s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
let settings = rmcp::schemars::gen::SchemaSettings::openapi3(); let settings = rmcp::schemars::generate::SchemaSettings::openapi3();
let generator = settings.into_generator(); let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<$inner_type>(); let schema = generator.into_root_schema_for::<$inner_type>();
let schema = convert_schemars_to_utoipa(schema); let schema = convert_schemars_to_utoipa(schema);
@@ -44,119 +43,128 @@ macro_rules! derive_utoipa {
}; };
} }
fn convert_schemars_to_utoipa(schema: rmcp::schemars::schema::RootSchema) -> RefOr<Schema> { fn convert_schemars_to_utoipa(schema: rmcp::schemars::Schema) -> RefOr<Schema> {
convert_schema_object(&rmcp::schemars::schema::Schema::Object( // For schemars 1.0+, we need to work with the public API
schema.schema.clone(), // The schema is now a wrapper around a JSON Value that can be either an object or bool
)) if let Some(true) = schema.as_bool() {
} return RefOr::T(Schema::Object(ObjectBuilder::new().build()));
fn convert_schema_object(schema: &rmcp::schemars::schema::Schema) -> RefOr<Schema> {
match schema {
rmcp::schemars::schema::Schema::Object(schema_object) => {
convert_schema_object_inner(schema_object)
}
rmcp::schemars::schema::Schema::Bool(true) => {
RefOr::T(Schema::Object(ObjectBuilder::new().build()))
}
rmcp::schemars::schema::Schema::Bool(false) => {
RefOr::T(Schema::Object(ObjectBuilder::new().build()))
}
} }
if let Some(false) = schema.as_bool() {
return RefOr::T(Schema::Object(ObjectBuilder::new().build()));
}
// For object schemas, we'll need to work with the JSON Value directly
if let Some(obj) = schema.as_object() {
return convert_json_object_to_utoipa(obj);
}
// Fallback
RefOr::T(Schema::Object(ObjectBuilder::new().build()))
} }
fn convert_schema_object_inner(schema: &SchemaObject) -> RefOr<Schema> { fn convert_json_object_to_utoipa(
// Handle references first obj: &serde_json::Map<String, serde_json::Value>,
if let Some(reference) = &schema.reference { ) -> RefOr<Schema> {
use serde_json::Value;
// Handle $ref
if let Some(Value::String(reference)) = obj.get("$ref") {
return RefOr::Ref(Ref::new(reference.clone())); return RefOr::Ref(Ref::new(reference.clone()));
} }
// Handle subschemas (oneOf, allOf, anyOf) // Handle oneOf, allOf, anyOf
if let Some(subschemas) = &schema.subschemas { if let Some(Value::Array(one_of)) = obj.get("oneOf") {
if let Some(one_of) = &subschemas.one_of { let mut builder = OneOfBuilder::new();
let schemas: Vec<RefOr<Schema>> = one_of.iter().map(convert_schema_object).collect(); for item in one_of {
let mut builder = OneOfBuilder::new(); if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
for schema in schemas { builder = builder.item(convert_schemars_to_utoipa(schema));
builder = builder.item(schema);
} }
return RefOr::T(Schema::OneOf(builder.build()));
}
if let Some(all_of) = &subschemas.all_of {
let schemas: Vec<RefOr<Schema>> = all_of.iter().map(convert_schema_object).collect();
let mut all_of = AllOfBuilder::new();
for schema in schemas {
all_of = all_of.item(schema);
}
return RefOr::T(Schema::AllOf(all_of.build()));
}
if let Some(any_of) = &subschemas.any_of {
let schemas: Vec<RefOr<Schema>> = any_of.iter().map(convert_schema_object).collect();
let mut any_of = AnyOfBuilder::new();
for schema in schemas {
any_of = any_of.item(schema);
}
return RefOr::T(Schema::AnyOf(any_of.build()));
} }
return RefOr::T(Schema::OneOf(builder.build()));
} }
// Handle based on instance type if let Some(Value::Array(all_of)) = obj.get("allOf") {
match &schema.instance_type { let mut builder = AllOfBuilder::new();
Some(SingleOrVec::Single(instance_type)) => { for item in all_of {
convert_single_instance_type(instance_type, schema) if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
} builder = builder.item(convert_schemars_to_utoipa(schema));
Some(SingleOrVec::Vec(instance_types)) => {
// Multiple types - use AnyOf
let schemas: Vec<RefOr<Schema>> = instance_types
.iter()
.map(|instance_type| convert_single_instance_type(instance_type, schema))
.collect();
let mut any_of = AnyOfBuilder::new();
for schema in schemas {
any_of = any_of.item(schema);
} }
RefOr::T(Schema::AnyOf(any_of.build()))
} }
None => { return RefOr::T(Schema::AllOf(builder.build()));
// No type specified - create a generic schema }
RefOr::T(Schema::Object(ObjectBuilder::new().build()))
if let Some(Value::Array(any_of)) = obj.get("anyOf") {
let mut builder = AnyOfBuilder::new();
for item in any_of {
if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
builder = builder.item(convert_schemars_to_utoipa(schema));
}
} }
return RefOr::T(Schema::AnyOf(builder.build()));
}
// Handle type-based schemas
match obj.get("type") {
Some(Value::String(type_str)) => convert_typed_schema(type_str, obj),
Some(Value::Array(types)) => {
// Multiple types - use AnyOf
let mut builder = AnyOfBuilder::new();
for type_val in types {
if let Value::String(type_str) = type_val {
builder = builder.item(convert_typed_schema(type_str, obj));
}
}
RefOr::T(Schema::AnyOf(builder.build()))
}
None => RefOr::T(Schema::Object(ObjectBuilder::new().build())),
_ => RefOr::T(Schema::Object(ObjectBuilder::new().build())), // Handle other value types
} }
} }
fn convert_single_instance_type( fn convert_typed_schema(
instance_type: &InstanceType, type_str: &str,
schema: &SchemaObject, obj: &serde_json::Map<String, serde_json::Value>,
) -> RefOr<Schema> { ) -> RefOr<Schema> {
match instance_type { use serde_json::Value;
InstanceType::Object => {
match type_str {
"object" => {
let mut object_builder = ObjectBuilder::new(); let mut object_builder = ObjectBuilder::new();
if let Some(object_validation) = &schema.object { // Add properties
// Add properties if let Some(Value::Object(properties)) = obj.get("properties") {
for (name, prop_schema) in &object_validation.properties { for (name, prop_value) in properties {
let prop = convert_schema_object(prop_schema); if let Ok(prop_schema) = rmcp::schemars::Schema::try_from(prop_value.clone()) {
object_builder = object_builder.property(name, prop); let prop = convert_schemars_to_utoipa(prop_schema);
object_builder = object_builder.property(name, prop);
}
} }
}
// Add required fields // Add required fields
for required_field in &object_validation.required { if let Some(Value::Array(required)) = obj.get("required") {
object_builder = object_builder.required(required_field); for req in required {
if let Value::String(field_name) = req {
object_builder = object_builder.required(field_name);
}
} }
}
// Handle additional properties // Handle additional properties
if let Some(additional) = &object_validation.additional_properties { if let Some(additional) = obj.get("additionalProperties") {
match &**additional { match additional {
rmcp::schemars::schema::Schema::Bool(false) => { Value::Bool(false) => {
object_builder = object_builder object_builder = object_builder
.additional_properties(Some(AdditionalProperties::FreeForm(false))); .additional_properties(Some(AdditionalProperties::FreeForm(false)));
} }
rmcp::schemars::schema::Schema::Bool(true) => { Value::Bool(true) => {
object_builder = object_builder object_builder = object_builder
.additional_properties(Some(AdditionalProperties::FreeForm(true))); .additional_properties(Some(AdditionalProperties::FreeForm(true)));
} }
rmcp::schemars::schema::Schema::Object(obj) => { _ => {
let schema = convert_schema_object( if let Ok(schema) = rmcp::schemars::Schema::try_from(additional.clone()) {
&rmcp::schemars::schema::Schema::Object(obj.clone()), let schema = convert_schemars_to_utoipa(schema);
);
object_builder = object_builder object_builder = object_builder
.additional_properties(Some(AdditionalProperties::RefOr(schema))); .additional_properties(Some(AdditionalProperties::RefOr(schema)));
} }
@@ -166,117 +174,140 @@ fn convert_single_instance_type(
RefOr::T(Schema::Object(object_builder.build())) RefOr::T(Schema::Object(object_builder.build()))
} }
InstanceType::Array => { "array" => {
let mut array_builder = ArrayBuilder::new(); let mut array_builder = ArrayBuilder::new();
if let Some(array_validation) = &schema.array { // Add items schema
// Add items schema if let Some(items) = obj.get("items") {
if let Some(items) = &array_validation.items { match items {
match items { Value::Object(_) | Value::Bool(_) => {
rmcp::schemars::schema::SingleOrVec::Single(item_schema) => { if let Ok(item_schema) = rmcp::schemars::Schema::try_from(items.clone()) {
let item_schema = convert_schema_object(item_schema); let item_schema = convert_schemars_to_utoipa(item_schema);
array_builder = array_builder.items(item_schema); array_builder = array_builder.items(item_schema);
} }
rmcp::schemars::schema::SingleOrVec::Vec(item_schemas) => {
// Multiple item types - use AnyOf
let schemas: Vec<RefOr<Schema>> =
item_schemas.iter().map(convert_schema_object).collect();
let mut any_of = AnyOfBuilder::new();
for schema in schemas {
any_of = any_of.item(schema);
}
let any_of_schema = RefOr::T(Schema::AnyOf(any_of.build()));
array_builder = array_builder.items(any_of_schema);
}
} }
Value::Array(item_schemas) => {
// Multiple item types - use AnyOf
let mut any_of = AnyOfBuilder::new();
for item in item_schemas {
if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
any_of = any_of.item(convert_schemars_to_utoipa(schema));
}
}
let any_of_schema = RefOr::T(Schema::AnyOf(any_of.build()));
array_builder = array_builder.items(any_of_schema);
}
_ => {}
} }
}
// Add constraints // Add constraints
if let Some(min_items) = array_validation.min_items { if let Some(Value::Number(min_items)) = obj.get("minItems") {
array_builder = array_builder.min_items(Some(min_items as usize)); if let Some(min) = min_items.as_u64() {
array_builder = array_builder.min_items(Some(min as usize));
} }
if let Some(max_items) = array_validation.max_items { }
array_builder = array_builder.max_items(Some(max_items as usize)); if let Some(Value::Number(max_items)) = obj.get("maxItems") {
if let Some(max) = max_items.as_u64() {
array_builder = array_builder.max_items(Some(max as usize));
} }
} }
RefOr::T(Schema::Array(array_builder.build())) RefOr::T(Schema::Array(array_builder.build()))
} }
InstanceType::String => { "string" => {
let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::String); let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::String);
if let Some(string_validation) = &schema.string { if let Some(Value::Number(min_length)) = obj.get("minLength") {
if let Some(min_length) = string_validation.min_length { if let Some(min) = min_length.as_u64() {
object_builder = object_builder.min_length(Some(min_length as usize)); object_builder = object_builder.min_length(Some(min as usize));
}
if let Some(max_length) = string_validation.max_length {
object_builder = object_builder.max_length(Some(max_length as usize));
}
if let Some(pattern) = &string_validation.pattern {
object_builder = object_builder.pattern(Some(pattern.clone()));
} }
} }
if let Some(Value::Number(max_length)) = obj.get("maxLength") {
if let Some(format) = &schema.format { if let Some(max) = max_length.as_u64() {
object_builder = object_builder.max_length(Some(max as usize));
}
}
if let Some(Value::String(pattern)) = obj.get("pattern") {
object_builder = object_builder.pattern(Some(pattern.clone()));
}
if let Some(Value::String(format)) = obj.get("format") {
object_builder = object_builder.format(Some(SchemaFormat::Custom(format.clone()))); object_builder = object_builder.format(Some(SchemaFormat::Custom(format.clone())));
} }
RefOr::T(Schema::Object(object_builder.build())) RefOr::T(Schema::Object(object_builder.build()))
} }
InstanceType::Number => { "number" => {
let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Number); let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Number);
if let Some(number_validation) = &schema.number { if let Some(Value::Number(minimum)) = obj.get("minimum") {
if let Some(minimum) = number_validation.minimum { if let Some(min) = minimum.as_f64() {
object_builder = object_builder.minimum(Some(minimum)); object_builder = object_builder.minimum(Some(min));
} }
if let Some(maximum) = number_validation.maximum { }
object_builder = object_builder.maximum(Some(maximum)); if let Some(Value::Number(maximum)) = obj.get("maximum") {
if let Some(max) = maximum.as_f64() {
object_builder = object_builder.maximum(Some(max));
} }
if let Some(exclusive_minimum) = number_validation.exclusive_minimum { }
object_builder = object_builder.exclusive_minimum(Some(exclusive_minimum)); if let Some(Value::Number(exclusive_minimum)) = obj.get("exclusiveMinimum") {
if let Some(min) = exclusive_minimum.as_f64() {
object_builder = object_builder.exclusive_minimum(Some(min));
} }
if let Some(exclusive_maximum) = number_validation.exclusive_maximum { }
object_builder = object_builder.exclusive_maximum(Some(exclusive_maximum)); if let Some(Value::Number(exclusive_maximum)) = obj.get("exclusiveMaximum") {
if let Some(max) = exclusive_maximum.as_f64() {
object_builder = object_builder.exclusive_maximum(Some(max));
} }
if let Some(multiple_of) = number_validation.multiple_of { }
object_builder = object_builder.multiple_of(Some(multiple_of)); if let Some(Value::Number(multiple_of)) = obj.get("multipleOf") {
if let Some(mult) = multiple_of.as_f64() {
object_builder = object_builder.multiple_of(Some(mult));
} }
} }
RefOr::T(Schema::Object(object_builder.build())) RefOr::T(Schema::Object(object_builder.build()))
} }
InstanceType::Integer => { "integer" => {
let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Integer); let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Integer);
if let Some(number_validation) = &schema.number { if let Some(Value::Number(minimum)) = obj.get("minimum") {
if let Some(minimum) = number_validation.minimum { if let Some(min) = minimum.as_f64() {
object_builder = object_builder.minimum(Some(minimum)); object_builder = object_builder.minimum(Some(min));
} }
if let Some(maximum) = number_validation.maximum { }
object_builder = object_builder.maximum(Some(maximum)); if let Some(Value::Number(maximum)) = obj.get("maximum") {
if let Some(max) = maximum.as_f64() {
object_builder = object_builder.maximum(Some(max));
} }
if let Some(exclusive_minimum) = number_validation.exclusive_minimum { }
object_builder = object_builder.exclusive_minimum(Some(exclusive_minimum)); if let Some(Value::Number(exclusive_minimum)) = obj.get("exclusiveMinimum") {
if let Some(min) = exclusive_minimum.as_f64() {
object_builder = object_builder.exclusive_minimum(Some(min));
} }
if let Some(exclusive_maximum) = number_validation.exclusive_maximum { }
object_builder = object_builder.exclusive_maximum(Some(exclusive_maximum)); if let Some(Value::Number(exclusive_maximum)) = obj.get("exclusiveMaximum") {
if let Some(max) = exclusive_maximum.as_f64() {
object_builder = object_builder.exclusive_maximum(Some(max));
} }
if let Some(multiple_of) = number_validation.multiple_of { }
object_builder = object_builder.multiple_of(Some(multiple_of)); if let Some(Value::Number(multiple_of)) = obj.get("multipleOf") {
if let Some(mult) = multiple_of.as_f64() {
object_builder = object_builder.multiple_of(Some(mult));
} }
} }
RefOr::T(Schema::Object(object_builder.build())) RefOr::T(Schema::Object(object_builder.build()))
} }
InstanceType::Boolean => RefOr::T(Schema::Object( "boolean" => RefOr::T(Schema::Object(
ObjectBuilder::new() ObjectBuilder::new()
.schema_type(SchemaType::Boolean) .schema_type(SchemaType::Boolean)
.build(), .build(),
)), )),
InstanceType::Null => RefOr::T(Schema::Object( "null" => RefOr::T(Schema::Object(
ObjectBuilder::new().schema_type(SchemaType::String).build(), ObjectBuilder::new().schema_type(SchemaType::String).build(),
)), )),
_ => RefOr::T(Schema::Object(ObjectBuilder::new().build())),
} }
} }
@@ -285,11 +316,40 @@ derive_utoipa!(Content as ContentSchema);
derive_utoipa!(EmbeddedResource as EmbeddedResourceSchema); derive_utoipa!(EmbeddedResource as EmbeddedResourceSchema);
derive_utoipa!(ImageContent as ImageContentSchema); derive_utoipa!(ImageContent as ImageContentSchema);
derive_utoipa!(TextContent as TextContentSchema); derive_utoipa!(TextContent as TextContentSchema);
derive_utoipa!(RawTextContent as RawTextContentSchema);
derive_utoipa!(RawImageContent as RawImageContentSchema);
derive_utoipa!(RawEmbeddedResource as RawEmbeddedResourceSchema);
derive_utoipa!(Tool as ToolSchema); derive_utoipa!(Tool as ToolSchema);
derive_utoipa!(ToolAnnotations as ToolAnnotationsSchema); derive_utoipa!(ToolAnnotations as ToolAnnotationsSchema);
derive_utoipa!(Annotations as AnnotationsSchema); derive_utoipa!(Annotations as AnnotationsSchema);
derive_utoipa!(ResourceContents as ResourceContentsSchema); derive_utoipa!(ResourceContents as ResourceContentsSchema);
// Create a manual schema for the generic Annotated type
// We manually define this to avoid circular references from RawContent::Audio(AudioContent)
// where AudioContent = Annotated<RawAudioContent>
struct AnnotatedSchema {}
impl<'__s> ToSchema<'__s> for AnnotatedSchema {
fn schema() -> (&'__s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
// Create a oneOf schema with only the variants we actually use in the API
// This avoids the circular reference from RawContent::Audio(AudioContent)
let schema = Schema::OneOf(
OneOfBuilder::new()
.item(RefOr::Ref(Ref::new("#/components/schemas/RawTextContent")))
.item(RefOr::Ref(Ref::new("#/components/schemas/RawImageContent")))
.item(RefOr::Ref(Ref::new(
"#/components/schemas/RawEmbeddedResource",
)))
.build(),
);
("Annotated", RefOr::T(schema))
}
fn aliases() -> Vec<(&'__s str, utoipa::openapi::schema::Schema)> {
Vec::new()
}
}
#[allow(dead_code)] // Used by utoipa for OpenAPI generation #[allow(dead_code)] // Used by utoipa for OpenAPI generation
#[derive(OpenApi)] #[derive(OpenApi)]
#[openapi( #[openapi(
@@ -349,6 +409,10 @@ derive_utoipa!(ResourceContents as ResourceContentsSchema);
ImageContentSchema, ImageContentSchema,
AnnotationsSchema, AnnotationsSchema,
TextContentSchema, TextContentSchema,
RawTextContentSchema,
RawImageContentSchema,
RawEmbeddedResourceSchema,
AnnotatedSchema,
ToolResponse, ToolResponse,
ToolRequest, ToolRequest,
ToolConfirmationRequest, ToolConfirmationRequest,
+90 -75
View File
@@ -1091,6 +1091,19 @@
} }
} }
}, },
"Annotated": {
"oneOf": [
{
"$ref": "#/components/schemas/RawTextContent"
},
{
"$ref": "#/components/schemas/RawImageContent"
},
{
"$ref": "#/components/schemas/RawEmbeddedResource"
}
]
},
"Annotations": { "Annotations": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1188,79 +1201,32 @@
"Content": { "Content": {
"oneOf": [ "oneOf": [
{ {
"type": "object", "allOf": [
"required": [ {
"text", "$ref": "#/components/schemas/RawTextContent"
"type"
],
"properties": {
"text": {
"type": "string"
},
"type": {
"type": "string"
} }
} ]
}, },
{ {
"type": "object", "allOf": [
"required": [ {
"data", "$ref": "#/components/schemas/RawImageContent"
"mimeType",
"type"
],
"properties": {
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
},
"type": {
"type": "string"
} }
} ]
}, },
{ {
"type": "object", "allOf": [
"required": [ {
"resource", "$ref": "#/components/schemas/RawEmbeddedResource"
"type"
],
"properties": {
"resource": {
"$ref": "#/components/schemas/ResourceContents"
},
"type": {
"type": "string"
} }
} ]
}, },
{ {
"type": "object", "allOf": [
"required": [ {
"data", "$ref": "#/components/schemas/Annotated"
"mimeType",
"type"
],
"properties": {
"annotations": {
"allOf": [
{
"$ref": "#/components/schemas/Annotations"
}
]
},
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
},
"type": {
"type": "string"
} }
} ]
} }
] ]
}, },
@@ -1427,9 +1393,12 @@
], ],
"properties": { "properties": {
"annotations": { "annotations": {
"allOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/Annotations" "$ref": "#/components/schemas/Annotations"
},
{
"type": "object"
} }
] ]
}, },
@@ -1828,9 +1797,12 @@
], ],
"properties": { "properties": {
"annotations": { "annotations": {
"allOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/Annotations" "$ref": "#/components/schemas/Annotations"
},
{
"type": "object"
} }
] ]
}, },
@@ -2288,6 +2260,43 @@
} }
} }
}, },
"RawEmbeddedResource": {
"type": "object",
"required": [
"resource"
],
"properties": {
"resource": {
"$ref": "#/components/schemas/ResourceContents"
}
}
},
"RawImageContent": {
"type": "object",
"required": [
"data",
"mimeType"
],
"properties": {
"data": {
"type": "string"
},
"mimeType": {
"type": "string"
}
}
},
"RawTextContent": {
"type": "object",
"required": [
"text"
],
"properties": {
"text": {
"type": "string"
}
}
},
"Recipe": { "Recipe": {
"type": "object", "type": "object",
"description": "A Recipe represents a personalized, user-generated agent configuration that defines\nspecific behaviors and capabilities within the Goose system.\n\n# Fields\n\n## Required Fields\n* `version` - Semantic version of the Recipe file format (defaults to \"1.0.0\")\n* `title` - Short, descriptive name of the Recipe\n* `description` - Detailed description explaining the Recipe's purpose and functionality\n* `Instructions` - Instructions that defines the Recipe's behavior\n\n## Optional Fields\n* `prompt` - the initial prompt to the session to start with\n* `extensions` - List of extension configurations required by the Recipe\n* `context` - Supplementary context information for the Recipe\n* `activities` - Activity labels that appear when loading the Recipe\n* `author` - Information about the Recipe's creator and metadata\n* `parameters` - Additional parameters for the Recipe\n* `response` - Response configuration including JSON schema validation\n* `retry` - Retry configuration for automated validation and recovery\n# Example\n\n\nuse goose::recipe::Recipe;\n\n// Using the builder pattern\nlet recipe = Recipe::builder()\n.title(\"Example Agent\")\n.description(\"An example Recipe configuration\")\n.instructions(\"Act as a helpful assistant\")\n.build()\n.expect(\"Missing required fields\");\n\n// Or using struct initialization\nlet recipe = Recipe {\nversion: \"1.0.0\".to_string(),\ntitle: \"Example Agent\".to_string(),\ndescription: \"An example Recipe configuration\".to_string(),\ninstructions: Some(\"Act as a helpful assistant\".to_string()),\nprompt: None,\nextensions: None,\ncontext: None,\nactivities: None,\nauthor: None,\nsettings: None,\nparameters: None,\nresponse: None,\nsub_recipes: None,\nretry: None,\n};\n", "description": "A Recipe represents a personalized, user-generated agent configuration that defines\nspecific behaviors and capabilities within the Goose system.\n\n# Fields\n\n## Required Fields\n* `version` - Semantic version of the Recipe file format (defaults to \"1.0.0\")\n* `title` - Short, descriptive name of the Recipe\n* `description` - Detailed description explaining the Recipe's purpose and functionality\n* `Instructions` - Instructions that defines the Recipe's behavior\n\n## Optional Fields\n* `prompt` - the initial prompt to the session to start with\n* `extensions` - List of extension configurations required by the Recipe\n* `context` - Supplementary context information for the Recipe\n* `activities` - Activity labels that appear when loading the Recipe\n* `author` - Information about the Recipe's creator and metadata\n* `parameters` - Additional parameters for the Recipe\n* `response` - Response configuration including JSON schema validation\n* `retry` - Retry configuration for automated validation and recovery\n# Example\n\n\nuse goose::recipe::Recipe;\n\n// Using the builder pattern\nlet recipe = Recipe::builder()\n.title(\"Example Agent\")\n.description(\"An example Recipe configuration\")\n.instructions(\"Act as a helpful assistant\")\n.build()\n.expect(\"Missing required fields\");\n\n// Or using struct initialization\nlet recipe = Recipe {\nversion: \"1.0.0\".to_string(),\ntitle: \"Example Agent\".to_string(),\ndescription: \"An example Recipe configuration\".to_string(),\ninstructions: Some(\"Act as a helpful assistant\".to_string()),\nprompt: None,\nextensions: None,\ncontext: None,\nactivities: None,\nauthor: None,\nsettings: None,\nparameters: None,\nresponse: None,\nsub_recipes: None,\nretry: None,\n};\n",
@@ -2451,11 +2460,11 @@
{ {
"type": "object", "type": "object",
"required": [ "required": [
"text", "uri",
"uri" "text"
], ],
"properties": { "properties": {
"mime_type": { "mimeType": {
"type": "string" "type": "string"
}, },
"text": { "text": {
@@ -2469,14 +2478,14 @@
{ {
"type": "object", "type": "object",
"required": [ "required": [
"blob", "uri",
"uri" "blob"
], ],
"properties": { "properties": {
"blob": { "blob": {
"type": "string" "type": "string"
}, },
"mime_type": { "mimeType": {
"type": "string" "type": "string"
}, },
"uri": { "uri": {
@@ -2898,9 +2907,12 @@
], ],
"properties": { "properties": {
"annotations": { "annotations": {
"allOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/Annotations" "$ref": "#/components/schemas/Annotations"
},
{
"type": "object"
} }
] ]
}, },
@@ -2927,14 +2939,17 @@
"Tool": { "Tool": {
"type": "object", "type": "object",
"required": [ "required": [
"inputSchema", "name",
"name" "inputSchema"
], ],
"properties": { "properties": {
"annotations": { "annotations": {
"allOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/ToolAnnotations" "$ref": "#/components/schemas/ToolAnnotations"
},
{
"type": "object"
} }
] ]
}, },
+30 -22
View File
@@ -8,6 +8,8 @@ export type AddSubRecipesResponse = {
success: boolean; success: boolean;
}; };
export type Annotated = RawTextContent | RawImageContent | RawEmbeddedResource;
export type Annotations = { export type Annotations = {
audience?: Array<Role>; audience?: Array<Role>;
priority?: number; priority?: number;
@@ -40,22 +42,7 @@ export type ConfigResponse = {
config: {}; config: {};
}; };
export type Content = { export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | Annotated;
text: string;
type: string;
} | {
data: string;
mimeType: string;
type: string;
} | {
resource: ResourceContents;
type: string;
} | {
annotations?: Annotations;
data: string;
mimeType: string;
type: string;
};
export type ContextLengthExceeded = { export type ContextLengthExceeded = {
msg: string; msg: string;
@@ -118,7 +105,9 @@ export type DecodeRecipeResponse = {
}; };
export type EmbeddedResource = { export type EmbeddedResource = {
annotations?: Annotations; annotations?: Annotations | {
[key: string]: unknown;
};
resource: ResourceContents; resource: ResourceContents;
}; };
@@ -265,7 +254,9 @@ export type FrontendToolRequest = {
}; };
export type ImageContent = { export type ImageContent = {
annotations?: Annotations; annotations?: Annotations | {
[key: string]: unknown;
};
data: string; data: string;
mimeType: string; mimeType: string;
}; };
@@ -407,6 +398,19 @@ export type ProvidersResponse = {
providers: Array<ProviderDetails>; providers: Array<ProviderDetails>;
}; };
export type RawEmbeddedResource = {
resource: ResourceContents;
};
export type RawImageContent = {
data: string;
mimeType: string;
};
export type RawTextContent = {
text: string;
};
/** /**
* A Recipe represents a personalized, user-generated agent configuration that defines * A Recipe represents a personalized, user-generated agent configuration that defines
* specific behaviors and capabilities within the Goose system. * specific behaviors and capabilities within the Goose system.
@@ -495,12 +499,12 @@ export type RedactedThinkingContent = {
}; };
export type ResourceContents = { export type ResourceContents = {
mime_type?: string; mimeType?: string;
text: string; text: string;
uri: string; uri: string;
} | { } | {
blob: string; blob: string;
mime_type?: string; mimeType?: string;
uri: string; uri: string;
}; };
@@ -679,7 +683,9 @@ export type SummarizationRequested = {
}; };
export type TextContent = { export type TextContent = {
annotations?: Annotations; annotations?: Annotations | {
[key: string]: unknown;
};
text: string; text: string;
}; };
@@ -689,7 +695,9 @@ export type ThinkingContent = {
}; };
export type Tool = { export type Tool = {
annotations?: ToolAnnotations; annotations?: ToolAnnotations | {
[key: string]: unknown;
};
description?: string; description?: string;
inputSchema: { inputSchema: {
[key: string]: unknown; [key: string]: unknown;
+3 -15
View File
@@ -6,26 +6,14 @@ interface FlyingBirdProps {
cycleInterval?: number; // milliseconds between bird frame changes cycleInterval?: number; // milliseconds between bird frame changes
} }
const birdFrames = [ const birdFrames = [Bird1, Bird2, Bird3, Bird4, Bird5, Bird6];
Bird1,
Bird2,
Bird3,
Bird4,
Bird5,
Bird6,
];
export default function FlyingBird({ export default function FlyingBird({ className = '', cycleInterval = 150 }: FlyingBirdProps) {
className = '',
cycleInterval = 150
}: FlyingBirdProps) {
const [currentFrameIndex, setCurrentFrameIndex] = useState(0); const [currentFrameIndex, setCurrentFrameIndex] = useState(0);
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
setCurrentFrameIndex((prevIndex) => setCurrentFrameIndex((prevIndex) => (prevIndex + 1) % birdFrames.length);
(prevIndex + 1) % birdFrames.length
);
}, cycleInterval); }, cycleInterval);
return () => clearInterval(interval); return () => clearInterval(interval);