diff --git a/Cargo.lock b/Cargo.lock index 19ccc01576a4..b434baefb901 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1981,8 +1981,18 @@ version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "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]] @@ -1999,13 +2009,38 @@ dependencies = [ "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]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 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", "syn 2.0.99", ] @@ -6761,9 +6796,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37f2048a81a7ff7e8ef6bc5abced70c3d9114c8f03d85d7aaaafd9fd04f12e9e" +checksum = "824daba0a34f8c5c5392295d381e0800f88fd986ba291699f8785f05fa344c1e" dependencies = [ "base64 0.22.1", "chrono", @@ -6782,11 +6817,11 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72398e694b9f6dbb5de960cf158c8699e6a1854cb5bbaac7de0646b2005763c4" +checksum = "ad6543c0572a4dbc125c23e6f54963ea9ba002294fd81dd4012c204219b0dcaa" dependencies = [ - "darling", + "darling 0.21.0", "proc-macro2", "quote", "serde_json", @@ -7072,12 +7107,13 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "chrono", "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -7085,9 +7121,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" dependencies = [ "proc-macro2", "quote", @@ -7278,7 +7314,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2", "quote", "syn 2.0.99", diff --git a/Cargo.toml b/Cargo.toml index cad9d62a672a..bbbe31185272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ description = "An AI agent" uninlined_format_args = "allow" [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.crates-io] diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index a4f0495f672d..38ea0e343e5c 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -12,12 +12,11 @@ use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata}; use goose::session::info::SessionInfo; use goose::session::SessionMetadata; use rmcp::model::{ - Annotations, Content, EmbeddedResource, ImageContent, ResourceContents, Role, TextContent, - Tool, ToolAnnotations, + Annotations, Content, EmbeddedResource, ImageContent, RawEmbeddedResource, RawImageContent, + RawTextContent, ResourceContents, Role, TextContent, Tool, ToolAnnotations, }; use utoipa::{OpenApi, ToSchema}; -use rmcp::schemars::schema::{InstanceType, SchemaObject, SingleOrVec}; use utoipa::openapi::schema::{ AdditionalProperties, AnyOfBuilder, ArrayBuilder, ObjectBuilder, OneOfBuilder, Schema, SchemaFormat, SchemaType, @@ -30,7 +29,7 @@ macro_rules! derive_utoipa { impl<'__s> ToSchema<'__s> for $schema_name { fn schema() -> (&'__s str, utoipa::openapi::RefOr) { - let settings = rmcp::schemars::gen::SchemaSettings::openapi3(); + let settings = rmcp::schemars::generate::SchemaSettings::openapi3(); let generator = settings.into_generator(); let schema = generator.into_root_schema_for::<$inner_type>(); 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 { - convert_schema_object(&rmcp::schemars::schema::Schema::Object( - schema.schema.clone(), - )) -} +fn convert_schemars_to_utoipa(schema: rmcp::schemars::Schema) -> RefOr { + // For schemars 1.0+, we need to work with the public API + // 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 { - 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 { - // Handle references first - if let Some(reference) = &schema.reference { +fn convert_json_object_to_utoipa( + obj: &serde_json::Map, +) -> RefOr { + use serde_json::Value; + + // Handle $ref + if let Some(Value::String(reference)) = obj.get("$ref") { return RefOr::Ref(Ref::new(reference.clone())); } - // Handle subschemas (oneOf, allOf, anyOf) - if let Some(subschemas) = &schema.subschemas { - if let Some(one_of) = &subschemas.one_of { - let schemas: Vec> = one_of.iter().map(convert_schema_object).collect(); - let mut builder = OneOfBuilder::new(); - for schema in schemas { - builder = builder.item(schema); + // Handle oneOf, allOf, anyOf + if let Some(Value::Array(one_of)) = obj.get("oneOf") { + let mut builder = OneOfBuilder::new(); + for item in one_of { + if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) { + builder = builder.item(convert_schemars_to_utoipa(schema)); } - return RefOr::T(Schema::OneOf(builder.build())); } - if let Some(all_of) = &subschemas.all_of { - let schemas: Vec> = 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::OneOf(builder.build())); + } + + if let Some(Value::Array(all_of)) = obj.get("allOf") { + let mut builder = AllOfBuilder::new(); + for item in all_of { + if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) { + builder = builder.item(convert_schemars_to_utoipa(schema)); } - return RefOr::T(Schema::AllOf(all_of.build())); } - if let Some(any_of) = &subschemas.any_of { - let schemas: Vec> = 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::AllOf(builder.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(any_of.build())); } + return RefOr::T(Schema::AnyOf(builder.build())); } - // Handle based on instance type - match &schema.instance_type { - Some(SingleOrVec::Single(instance_type)) => { - convert_single_instance_type(instance_type, schema) - } - Some(SingleOrVec::Vec(instance_types)) => { + // 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 schemas: Vec> = 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); + 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(any_of.build())) - } - None => { - // No type specified - create a generic schema - RefOr::T(Schema::Object(ObjectBuilder::new().build())) + 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( - instance_type: &InstanceType, - schema: &SchemaObject, +fn convert_typed_schema( + type_str: &str, + obj: &serde_json::Map, ) -> RefOr { - match instance_type { - InstanceType::Object => { + use serde_json::Value; + + match type_str { + "object" => { let mut object_builder = ObjectBuilder::new(); - if let Some(object_validation) = &schema.object { - // Add properties - for (name, prop_schema) in &object_validation.properties { - let prop = convert_schema_object(prop_schema); - object_builder = object_builder.property(name, prop); + // Add properties + if let Some(Value::Object(properties)) = obj.get("properties") { + for (name, prop_value) in properties { + if let Ok(prop_schema) = rmcp::schemars::Schema::try_from(prop_value.clone()) { + let prop = convert_schemars_to_utoipa(prop_schema); + object_builder = object_builder.property(name, prop); + } } + } - // Add required fields - for required_field in &object_validation.required { - object_builder = object_builder.required(required_field); + // Add required fields + if let Some(Value::Array(required)) = obj.get("required") { + for req in required { + if let Value::String(field_name) = req { + object_builder = object_builder.required(field_name); + } } + } - // Handle additional properties - if let Some(additional) = &object_validation.additional_properties { - match &**additional { - rmcp::schemars::schema::Schema::Bool(false) => { - object_builder = object_builder - .additional_properties(Some(AdditionalProperties::FreeForm(false))); - } - rmcp::schemars::schema::Schema::Bool(true) => { - object_builder = object_builder - .additional_properties(Some(AdditionalProperties::FreeForm(true))); - } - rmcp::schemars::schema::Schema::Object(obj) => { - let schema = convert_schema_object( - &rmcp::schemars::schema::Schema::Object(obj.clone()), - ); + // Handle additional properties + if let Some(additional) = obj.get("additionalProperties") { + match additional { + Value::Bool(false) => { + object_builder = object_builder + .additional_properties(Some(AdditionalProperties::FreeForm(false))); + } + Value::Bool(true) => { + object_builder = object_builder + .additional_properties(Some(AdditionalProperties::FreeForm(true))); + } + _ => { + if let Ok(schema) = rmcp::schemars::Schema::try_from(additional.clone()) { + let schema = convert_schemars_to_utoipa(schema); object_builder = object_builder .additional_properties(Some(AdditionalProperties::RefOr(schema))); } @@ -166,117 +174,140 @@ fn convert_single_instance_type( RefOr::T(Schema::Object(object_builder.build())) } - InstanceType::Array => { + "array" => { let mut array_builder = ArrayBuilder::new(); - if let Some(array_validation) = &schema.array { - // Add items schema - if let Some(items) = &array_validation.items { - match items { - rmcp::schemars::schema::SingleOrVec::Single(item_schema) => { - let item_schema = convert_schema_object(item_schema); + // Add items schema + if let Some(items) = obj.get("items") { + match items { + Value::Object(_) | Value::Bool(_) => { + if let Ok(item_schema) = rmcp::schemars::Schema::try_from(items.clone()) { + let item_schema = convert_schemars_to_utoipa(item_schema); array_builder = array_builder.items(item_schema); } - rmcp::schemars::schema::SingleOrVec::Vec(item_schemas) => { - // Multiple item types - use AnyOf - let schemas: Vec> = - item_schemas.iter().map(convert_schema_object).collect(); - let mut any_of = AnyOfBuilder::new(); - for schema in schemas { - any_of = any_of.item(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); } + let any_of_schema = RefOr::T(Schema::AnyOf(any_of.build())); + array_builder = array_builder.items(any_of_schema); } + _ => {} } + } - // Add constraints - if let Some(min_items) = array_validation.min_items { - array_builder = array_builder.min_items(Some(min_items as usize)); + // Add constraints + if let Some(Value::Number(min_items)) = obj.get("minItems") { + 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())) } - InstanceType::String => { + "string" => { let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::String); - if let Some(string_validation) = &schema.string { - if let Some(min_length) = string_validation.min_length { - object_builder = object_builder.min_length(Some(min_length 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(Value::Number(min_length)) = obj.get("minLength") { + if let Some(min) = min_length.as_u64() { + object_builder = object_builder.min_length(Some(min 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(max) = max_length.as_u64() { + object_builder = object_builder.max_length(Some(max as usize)); } } - - if let Some(format) = &schema.format { + 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()))); } RefOr::T(Schema::Object(object_builder.build())) } - InstanceType::Number => { + "number" => { let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Number); - if let Some(number_validation) = &schema.number { - if let Some(minimum) = number_validation.minimum { - object_builder = object_builder.minimum(Some(minimum)); + if let Some(Value::Number(minimum)) = obj.get("minimum") { + if let Some(min) = minimum.as_f64() { + 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())) } - InstanceType::Integer => { + "integer" => { let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Integer); - if let Some(number_validation) = &schema.number { - if let Some(minimum) = number_validation.minimum { - object_builder = object_builder.minimum(Some(minimum)); + if let Some(Value::Number(minimum)) = obj.get("minimum") { + if let Some(min) = minimum.as_f64() { + 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())) } - InstanceType::Boolean => RefOr::T(Schema::Object( + "boolean" => RefOr::T(Schema::Object( ObjectBuilder::new() .schema_type(SchemaType::Boolean) .build(), )), - InstanceType::Null => RefOr::T(Schema::Object( + "null" => RefOr::T(Schema::Object( 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!(ImageContent as ImageContentSchema); 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!(ToolAnnotations as ToolAnnotationsSchema); derive_utoipa!(Annotations as AnnotationsSchema); 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 +struct AnnotatedSchema {} + +impl<'__s> ToSchema<'__s> for AnnotatedSchema { + fn schema() -> (&'__s str, utoipa::openapi::RefOr) { + // 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 #[derive(OpenApi)] #[openapi( @@ -349,6 +409,10 @@ derive_utoipa!(ResourceContents as ResourceContentsSchema); ImageContentSchema, AnnotationsSchema, TextContentSchema, + RawTextContentSchema, + RawImageContentSchema, + RawEmbeddedResourceSchema, + AnnotatedSchema, ToolResponse, ToolRequest, ToolConfirmationRequest, diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 7f4190fbe85d..34850d03d3c9 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1091,6 +1091,19 @@ } } }, + "Annotated": { + "oneOf": [ + { + "$ref": "#/components/schemas/RawTextContent" + }, + { + "$ref": "#/components/schemas/RawImageContent" + }, + { + "$ref": "#/components/schemas/RawEmbeddedResource" + } + ] + }, "Annotations": { "type": "object", "properties": { @@ -1188,79 +1201,32 @@ "Content": { "oneOf": [ { - "type": "object", - "required": [ - "text", - "type" - ], - "properties": { - "text": { - "type": "string" - }, - "type": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/RawTextContent" } - } + ] }, { - "type": "object", - "required": [ - "data", - "mimeType", - "type" - ], - "properties": { - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/RawImageContent" } - } + ] }, { - "type": "object", - "required": [ - "resource", - "type" - ], - "properties": { - "resource": { - "$ref": "#/components/schemas/ResourceContents" - }, - "type": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/RawEmbeddedResource" } - } + ] }, { - "type": "object", - "required": [ - "data", - "mimeType", - "type" - ], - "properties": { - "annotations": { - "allOf": [ - { - "$ref": "#/components/schemas/Annotations" - } - ] - }, - "data": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "type": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/Annotated" } - } + ] } ] }, @@ -1427,9 +1393,12 @@ ], "properties": { "annotations": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Annotations" + }, + { + "type": "object" } ] }, @@ -1828,9 +1797,12 @@ ], "properties": { "annotations": { - "allOf": [ + "anyOf": [ { "$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": { "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", @@ -2451,11 +2460,11 @@ { "type": "object", "required": [ - "text", - "uri" + "uri", + "text" ], "properties": { - "mime_type": { + "mimeType": { "type": "string" }, "text": { @@ -2469,14 +2478,14 @@ { "type": "object", "required": [ - "blob", - "uri" + "uri", + "blob" ], "properties": { "blob": { "type": "string" }, - "mime_type": { + "mimeType": { "type": "string" }, "uri": { @@ -2898,9 +2907,12 @@ ], "properties": { "annotations": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Annotations" + }, + { + "type": "object" } ] }, @@ -2927,14 +2939,17 @@ "Tool": { "type": "object", "required": [ - "inputSchema", - "name" + "name", + "inputSchema" ], "properties": { "annotations": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ToolAnnotations" + }, + { + "type": "object" } ] }, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 19a8a8952032..79dd33acf3b1 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -8,6 +8,8 @@ export type AddSubRecipesResponse = { success: boolean; }; +export type Annotated = RawTextContent | RawImageContent | RawEmbeddedResource; + export type Annotations = { audience?: Array; priority?: number; @@ -40,22 +42,7 @@ export type ConfigResponse = { config: {}; }; -export type Content = { - text: string; - type: string; -} | { - data: string; - mimeType: string; - type: string; -} | { - resource: ResourceContents; - type: string; -} | { - annotations?: Annotations; - data: string; - mimeType: string; - type: string; -}; +export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | Annotated; export type ContextLengthExceeded = { msg: string; @@ -118,7 +105,9 @@ export type DecodeRecipeResponse = { }; export type EmbeddedResource = { - annotations?: Annotations; + annotations?: Annotations | { + [key: string]: unknown; + }; resource: ResourceContents; }; @@ -265,7 +254,9 @@ export type FrontendToolRequest = { }; export type ImageContent = { - annotations?: Annotations; + annotations?: Annotations | { + [key: string]: unknown; + }; data: string; mimeType: string; }; @@ -407,6 +398,19 @@ export type ProvidersResponse = { providers: Array; }; +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 * specific behaviors and capabilities within the Goose system. @@ -495,12 +499,12 @@ export type RedactedThinkingContent = { }; export type ResourceContents = { - mime_type?: string; + mimeType?: string; text: string; uri: string; } | { blob: string; - mime_type?: string; + mimeType?: string; uri: string; }; @@ -679,7 +683,9 @@ export type SummarizationRequested = { }; export type TextContent = { - annotations?: Annotations; + annotations?: Annotations | { + [key: string]: unknown; + }; text: string; }; @@ -689,7 +695,9 @@ export type ThinkingContent = { }; export type Tool = { - annotations?: ToolAnnotations; + annotations?: ToolAnnotations | { + [key: string]: unknown; + }; description?: string; inputSchema: { [key: string]: unknown; diff --git a/ui/desktop/src/components/FlyingBird.tsx b/ui/desktop/src/components/FlyingBird.tsx index 93baa3f5be6c..d4c8c55ecf5e 100644 --- a/ui/desktop/src/components/FlyingBird.tsx +++ b/ui/desktop/src/components/FlyingBird.tsx @@ -6,26 +6,14 @@ interface FlyingBirdProps { cycleInterval?: number; // milliseconds between bird frame changes } -const birdFrames = [ - Bird1, - Bird2, - Bird3, - Bird4, - Bird5, - Bird6, -]; +const birdFrames = [Bird1, Bird2, Bird3, Bird4, Bird5, Bird6]; -export default function FlyingBird({ - className = '', - cycleInterval = 150 -}: FlyingBirdProps) { +export default function FlyingBird({ className = '', cycleInterval = 150 }: FlyingBirdProps) { const [currentFrameIndex, setCurrentFrameIndex] = useState(0); useEffect(() => { const interval = setInterval(() => { - setCurrentFrameIndex((prevIndex) => - (prevIndex + 1) % birdFrames.length - ); + setCurrentFrameIndex((prevIndex) => (prevIndex + 1) % birdFrames.length); }, cycleInterval); return () => clearInterval(interval);