diff --git a/integration-tests/tests/custom_api/agent_http_principal_rust.rs b/integration-tests/tests/custom_api/agent_http_principal_rust.rs new file mode 100644 index 0000000000..5eaca2059c --- /dev/null +++ b/integration-tests/tests/custom_api/agent_http_principal_rust.rs @@ -0,0 +1,210 @@ +// Copyright 2024-2026 Golem Cloud +// +// Licensed under the Golem Source License v1.1 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://license.golem.cloud/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::custom_api::http_test_context::{HttpTestContext, make_test_context}; +use golem_common::base_model::agent::AgentTypeName; +use golem_common::base_model::http_api_deployment::HttpApiDeploymentAgentOptions; +use golem_common::model::http_api_deployment::{ + HttpApiDeploymentAgentSecurity, TestSessionHeaderAgentSecurity, +}; +use golem_test_framework::config::EnvBasedTestDependencies; +use pretty_assertions::assert_eq; +use serde_json::json; +use test_r::test_dep; +use test_r::{inherit_test_dep, test}; + +inherit_test_dep!(EnvBasedTestDependencies); + +#[test_dep] +async fn test_context(deps: &EnvBasedTestDependencies) -> HttpTestContext { + make_test_context( + deps, + vec![( + AgentTypeName("PrincipalAgent".to_string()), + HttpApiDeploymentAgentOptions { + security: Some(HttpApiDeploymentAgentSecurity::TestSessionHeader( + TestSessionHeaderAgentSecurity { + header_name: "x-golem-test-session".to_string(), + }, + )), + }, + )], + "golem_it_agent_sdk_rust_release", + "golem-it:agent-sdk-rust", + ) + .await + .unwrap() +} + +#[test] +#[tracing::instrument] +async fn principal_auto_injection(agent: &HttpTestContext) -> anyhow::Result<()> { + let response = agent + .client + .get( + agent + .base_url + .join("/principal-agent/test-agent/echo-principal")?, + ) + .send() + .await?; + + assert_eq!(response.status(), reqwest::StatusCode::OK); + + let body: serde_json::Value = response.json().await?; + assert_eq!(body, json!({ "value": {"anonymous": null} })); + + Ok(()) +} + +#[test] +#[tracing::instrument] +async fn principal_auto_injection_middle_segment(agent: &HttpTestContext) -> anyhow::Result<()> { + let response = agent + .client + .get( + agent + .base_url + .join("/principal-agent/test-agent/echo-principal-mid/foo-value/1")?, + ) + .send() + .await?; + + let body: serde_json::Value = response.json().await?; + + assert_eq!( + body, + json!({ "value": {"anonymous": null}, "foo": "foo-value", "bar": 1 }) + ); + + Ok(()) +} + +#[test] +#[tracing::instrument] +async fn principal_auto_injection_last_segment(agent: &HttpTestContext) -> anyhow::Result<()> { + let response = agent + .client + .get( + agent + .base_url + .join("/principal-agent/test-agent/echo-principal-last/foo-value/2")?, + ) + .send() + .await?; + + let body: serde_json::Value = response.json().await?; + + assert_eq!( + body, + json!({ "value": {"anonymous": null}, "foo": "foo-value", "bar": 2 }) + ); + + Ok(()) +} + +#[test] +#[tracing::instrument] +async fn default_test_header_oidc_principal(agent: &HttpTestContext) -> anyhow::Result<()> { + let response = agent + .client + .get( + agent + .base_url + .join("/principal-agent/test-agent/authed-principal")?, + ) + .header("x-golem-test-session", "{}") + .send() + .await?; + + let mut body: serde_json::Value = response.json().await?; + // claims include exp, which is not deterministic in tests + if let Some(oidc) = body + .get_mut("value") + .and_then(|v| v.get_mut("oidc")) + .and_then(|v| v.as_object_mut()) + { + oidc.remove("claims"); + } + + assert_eq!( + body, + json!({ + "value": { + "oidc": { + "email": null, + "email_verified": null, + "family_name": null, + "given_name": null, + "issuer": "http://test-idp.com", + "name": null, + "picture": null, + "preferred_username": null, + "sub": "test-user", + } + } + }) + ); + + Ok(()) +} + +#[test] +#[tracing::instrument] +async fn test_header_oidc_principal_with_overrides(agent: &HttpTestContext) -> anyhow::Result<()> { + let response = agent + .client + .get( + agent + .base_url + .join("/principal-agent/test-agent/authed-principal")?, + ) + .header( + "x-golem-test-session", + "{ \"subject\": \"bob\", \"email\": \"bob@golem.cloud\"}", + ) + .send() + .await?; + + let mut body: serde_json::Value = response.json().await?; + // claims include exp, which is not deterministic in tests + if let Some(oidc) = body + .get_mut("value") + .and_then(|v| v.get_mut("oidc")) + .and_then(|v| v.as_object_mut()) + { + oidc.remove("claims"); + } + + assert_eq!( + body, + json!({ + "value": { + "oidc": { + "email": "bob@golem.cloud", + "email_verified": null, + "family_name": null, + "given_name": null, + "issuer": "http://test-idp.com", + "name": null, + "picture": null, + "preferred_username": null, + "sub": "bob", + } + } + }) + ); + + Ok(()) +} diff --git a/integration-tests/tests/custom_api/mod.rs b/integration-tests/tests/custom_api/mod.rs index d1deff6a45..9c60ee30a4 100644 --- a/integration-tests/tests/custom_api/mod.rs +++ b/integration-tests/tests/custom_api/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod agent_http_principal_rust; mod agent_http_principal_ts; mod agent_http_routes_rust; mod agent_http_routes_ts; diff --git a/sdks/rust/Cargo.lock b/sdks/rust/Cargo.lock index 5eaec69843..3bc2831ed7 100644 --- a/sdks/rust/Cargo.lock +++ b/sdks/rust/Cargo.lock @@ -666,7 +666,9 @@ dependencies = [ [[package]] name = "golem-wasm" -version = "0.0.0" +version = "1.5.0-dev.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a0ebb5ea8461b3dce6007eac76911835a030bafd8eff1d581af99d33f417e" dependencies = [ "chrono", "itertools", diff --git a/sdks/rust/golem-rust-macro/src/agentic/agent_definition_impl.rs b/sdks/rust/golem-rust-macro/src/agentic/agent_definition_impl.rs index 9d8f71cdf5..177891ab25 100644 --- a/sdks/rust/golem-rust-macro/src/agentic/agent_definition_impl.rs +++ b/sdks/rust/golem-rust-macro/src/agentic/agent_definition_impl.rs @@ -19,8 +19,8 @@ use crate::agentic::agent_definition_http_endpoint::{ ParsedHttpEndpointDetails, extract_http_endpoints, }; use crate::agentic::helpers::{ - AgentConfigAttrRemover, has_agent_config_attr, is_async_trait_attr, is_constructor_method, - is_static_method, + GolemAgentAttrsRemover, has_agent_config_attr, has_principal_attr, is_async_trait_attr, + is_constructor_method, is_static_method, }; use crate::agentic::{ async_trait_in_agent_definition_error, endpoint_on_constructor_method_error, @@ -75,14 +75,12 @@ pub fn agent_definition_impl(attrs: TokenStream, item: TokenStream) -> TokenStre let registration_function: syn::TraitItem = syn::parse_quote! { fn __register_agent_type() { let agent_type = #agent_type; - let principal_input_parameters = agent_type.principal_params_in_constructor(); if let Some(http_mount) = &agent_type.http_mount { golem_rust::agentic::validate_http_mount( &agent_type.type_name, &http_mount, - &agent_type.constructor.to_agent_constructor(), - &principal_input_parameters + &agent_type.constructor ).expect("HTTP mount validation failed"); } @@ -108,7 +106,7 @@ pub fn agent_definition_impl(attrs: TokenStream, item: TokenStream) -> TokenStre agent_definition_trait.items.push(save_snapshot_item); agent_definition_trait.items.push(registration_function); - AgentConfigAttrRemover.visit_item_trait_mut(&mut agent_definition_trait); + GolemAgentAttrsRemover.visit_item_trait_mut(&mut agent_definition_trait); let result = quote! { #[allow(async_fn_in_trait)] @@ -284,23 +282,22 @@ fn get_agent_type_with_remote_client( return generic_type_in_agent_method_error(pat_type.pat.span(), &type_name).into(); } - if !has_agent_config_attr(pat_type) { - let ty = &pat_type.ty; - input_schema_logic.push(quote! { - let schema: golem_rust::agentic::StructuredSchema = <#ty as golem_rust::agentic::Schema>::get_type(); - match schema { - golem_rust::agentic::StructuredSchema::Default(element_schema) => { - default_inputs.push((#param_name.to_string(), golem_rust::agentic::EnrichedElementSchema::ElementSchema(element_schema))); - }, - golem_rust::agentic::StructuredSchema::Multimodal(name_and_types) => { - multi_modal_inputs.extend(name_and_types); - }, - golem_rust::agentic::StructuredSchema::AutoInject(auto_inject_schema) => { - default_inputs.push((#param_name.to_string(), golem_rust::agentic::EnrichedElementSchema::AutoInject(auto_inject_schema))); - } - } - }); + if has_agent_config_attr(pat_type) || has_principal_attr(pat_type) { + continue; } + + let ty = &pat_type.ty; + input_schema_logic.push(quote! { + let schema: golem_rust::agentic::StructuredSchema = <#ty as golem_rust::agentic::Schema>::get_type(); + match schema { + golem_rust::agentic::StructuredSchema::Default(element_schema) => { + default_inputs.push((#param_name.to_string(), element_schema)); + }, + golem_rust::agentic::StructuredSchema::Multimodal(name_and_types) => { + multi_modal_inputs.extend(name_and_types); + } + } + }); } } @@ -326,13 +323,10 @@ fn get_agent_type_with_remote_client( let schema = <#ty as golem_rust::agentic::Schema>::get_type(); match schema { golem_rust::agentic::StructuredSchema::Default(element_schema) => { - default_outputs.push(("return_value".to_string(), golem_rust::agentic::EnrichedElementSchema::ElementSchema(element_schema))); + default_outputs.push(("return_value".to_string(), element_schema)); }, golem_rust::agentic::StructuredSchema::Multimodal(name_and_types) => { multi_modal_outputs.extend(name_and_types) - }, - golem_rust::agentic::StructuredSchema::AutoInject(auto_injected_schema) => { - default_outputs.push(("return_value".to_string(), golem_rust::agentic::EnrichedElementSchema::AutoInject(auto_injected_schema))); } } }); @@ -345,9 +339,9 @@ fn get_agent_type_with_remote_client( #input_schema_token #(#input_schema_logic)* if !multi_modal_inputs.is_empty() { - golem_rust::agentic::ExtendedDataSchema::Multimodal(multi_modal_inputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Multimodal(multi_modal_inputs) } else { - golem_rust::agentic::ExtendedDataSchema::Tuple(default_inputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Tuple(default_inputs) } } }; @@ -357,15 +351,15 @@ fn get_agent_type_with_remote_client( #output_schema_token #(#output_schema_logic)* if !multi_modal_outputs.is_empty() { - golem_rust::agentic::ExtendedDataSchema::Multimodal(multi_modal_outputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Multimodal(multi_modal_outputs) } else { - golem_rust::agentic::ExtendedDataSchema::Tuple(default_outputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Tuple(default_outputs) } } }; Some(quote! { - golem_rust::agentic::EnrichedAgentMethod { + golem_rust::golem_agentic::golem::agent::common::AgentMethod { name: #method_name.to_string(), description: #method_description.to_string(), prompt_hint: { @@ -448,7 +442,9 @@ fn get_agent_type_with_remote_client( #param_ident: <<#ty as ::golem_rust::agentic::InnerTypeHelper>::Type as ::golem_rust::agentic::ConfigSchema>::RpcType }); client_agent_config_param_idents.push(param_ident.clone()); - } else if type_name != "Principal" { + } else if has_principal_attr(pat_type) { + // principals are not passed as parameters to clients + } else { client_data_value_param_defs.push(quote! { #param_ident: #ty }); @@ -460,19 +456,16 @@ fn get_agent_type_with_remote_client( _ => "_".to_string(), }; - if !has_agent_config_attr(pat_type) { + if !has_agent_config_attr(pat_type) && !has_principal_attr(pat_type) { let ty = &pat_type.ty; constructor_parameters_with_schema.push(quote! { let schema: golem_rust::agentic::StructuredSchema = <#ty as golem_rust::agentic::Schema>::get_type(); match schema { golem_rust::agentic::StructuredSchema::Default(element_schema) => { - constructor_default_inputs.push((#param_name.to_string(), golem_rust::agentic::EnrichedElementSchema::ElementSchema(element_schema))); + constructor_default_inputs.push((#param_name.to_string(), element_schema)); }, golem_rust::agentic::StructuredSchema::Multimodal(name_and_types) => { constructor_multi_modal_inputs.extend(name_and_types); - }, - golem_rust::agentic::StructuredSchema::AutoInject(auto_inject_schema) => { - constructor_default_inputs.push((#param_name.to_string(), golem_rust::agentic::EnrichedElementSchema::AutoInject(auto_inject_schema))); } } }); @@ -486,9 +479,9 @@ fn get_agent_type_with_remote_client( #constructor_schema_init #(#constructor_parameters_with_schema)* if !constructor_multi_modal_inputs.is_empty() { - golem_rust::agentic::ExtendedDataSchema::Multimodal(constructor_multi_modal_inputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Multimodal(constructor_multi_modal_inputs) } else { - golem_rust::agentic::ExtendedDataSchema::Tuple(constructor_default_inputs) + golem_rust::golem_agentic::golem::agent::common::DataSchema::Tuple(constructor_default_inputs) } } }; @@ -522,14 +515,14 @@ fn get_agent_type_with_remote_client( let agent_constructor = quote! { { - #constructor_data_schema_token - - golem_rust::agentic::ExtendedAgentConstructor { - name: #constructor_name, - description: #constructor_description.to_string(), - prompt_hint: #constructor_prompt_hint, - input_schema: constructor_data_schema, - } + #constructor_data_schema_token + + golem_rust::golem_agentic::golem::agent::common::AgentConstructor { + name: #constructor_name, + description: #constructor_description.to_string(), + prompt_hint: #constructor_prompt_hint, + input_schema: constructor_data_schema, + } } }; @@ -555,7 +548,7 @@ fn get_agent_type_with_remote_client( Ok(AgentTypeWithRemoteClient { agent_type: quote! { - golem_rust::agentic::ExtendedAgentType { + golem_rust::golem_agentic::golem::agent::common::AgentType { type_name: #agent_trait_name.to_string(), description: #high_level_description_ident.to_string(), source_language: "rust".to_string(), @@ -565,8 +558,7 @@ fn get_agent_type_with_remote_client( mode: #mode_value, http_mount: #http_options, snapshotting: #snapshotting_value, - config: #config_impl, - sorted_method_indices: vec![], + config: #config_impl } }, remote_client, diff --git a/sdks/rust/golem-rust-macro/src/agentic/agent_implementation_impl.rs b/sdks/rust/golem-rust-macro/src/agentic/agent_implementation_impl.rs index 718c854239..ae88427e53 100644 --- a/sdks/rust/golem-rust-macro/src/agentic/agent_implementation_impl.rs +++ b/sdks/rust/golem-rust-macro/src/agentic/agent_implementation_impl.rs @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::agentic::helpers::{ + Asyncness, FunctionOutputInfo, GolemAgentAttrsRemover, get_asyncness, has_agent_config_attr, + has_async_trait_attr, has_principal_attr, is_constructor_method, is_static_method, + trim_type_parameter, +}; use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::ItemImpl; - -use crate::agentic::helpers::{ - AgentConfigAttrRemover, Asyncness, FunctionOutputInfo, get_asyncness, has_agent_config_attr, - has_async_trait_attr, is_constructor_method, is_static_method, trim_type_parameter, -}; use syn::visit_mut::VisitMut; pub fn agent_implementation_impl(_attrs: TokenStream, item: TokenStream) -> TokenStream { @@ -145,7 +145,7 @@ pub fn agent_implementation_impl(_attrs: TokenStream, item: TokenStream) -> Toke let register_initiator_fn = generate_register_initiator_fn(&impl_block.self_ty, &trait_name_ident, &initiator_ident); - AgentConfigAttrRemover.visit_item_impl_mut(&mut impl_block); + GolemAgentAttrsRemover.visit_item_impl_mut(&mut impl_block); quote! { #impl_block @@ -202,7 +202,7 @@ fn build_match_arms( struct MethodInfo<'a> { method: &'a syn::ImplItemFn, name: String, - param_idents: Vec, + param_idents_and_types: Vec<(syn::Ident, syn::PatType)>, } let mut eligible_methods: Vec = Vec::new(); @@ -232,15 +232,12 @@ fn build_match_arms( } let name = method.sig.ident.to_string(); - let param_idents: Vec = extract_param_idents(method) - .into_iter() - .map(|(ident, _)| ident) - .collect(); + let param_idents_and_types = extract_param_idents(method); eligible_methods.push(MethodInfo { method, name, - param_idents, + param_idents_and_types, }); } } @@ -251,7 +248,11 @@ fn build_match_arms( // Second pass: generate match arms with sorted method indices for (sorted_method_index, info) in eligible_methods.iter().enumerate() { let method_name = &info.name; - let param_idents = &info.param_idents; + let param_idents_and_types = &info.param_idents_and_types; + let param_idents: Vec = param_idents_and_types + .iter() + .map(|(ident, _)| ident.clone()) + .collect(); let ident = &info.method.sig.ident; let fn_output_info = FunctionOutputInfo::from_signature(&info.method.sig); @@ -271,12 +272,6 @@ fn build_match_arms( }, golem_rust::agentic::StructuredValue::Multimodal(result) => { Ok(golem_rust::golem_agentic::golem::agent::common::DataValue::Multimodal(result)) - }, - golem_rust::agentic::StructuredValue::AutoInjected(_) => { - Err(golem_rust::agentic::custom_error(format!( - "Principal value cannot be returned from method {}", - #method_name - ))) } } }) @@ -299,12 +294,6 @@ fn build_match_arms( }, golem_rust::agentic::StructuredValue::Multimodal(result) => { Ok(golem_rust::golem_agentic::golem::agent::common::DataValue::Multimodal(result)) - }, - golem_rust::agentic::StructuredValue::AutoInjected(_) => { - Err(golem_rust::agentic::custom_error(format!( - "Principal value cannot be returned from method {}", - #method_name - ))) } } }) @@ -316,7 +305,7 @@ fn build_match_arms( }; let method_param_extraction = generate_method_param_extraction( - param_idents, + param_idents_and_types, &agent_type_name, method_name.as_str(), sorted_method_index, @@ -334,100 +323,121 @@ fn build_match_arms( } fn generate_method_param_extraction( - param_idents: &[syn::Ident], + param_idents_and_types: &[(syn::Ident, syn::PatType)], agent_type_name: &str, method_name: &str, sorted_method_index: usize, post_method_param_extraction_logic: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let input_param_index_init = quote! { - let mut input_param_index: usize = 0; - let __agent_type_name = golem_rust::agentic::AgentTypeName(#agent_type_name.to_string()); - let __param_schemas = golem_rust::agentic::get_method_parameter_types_by_index( - &__agent_type_name, - #sorted_method_index - ).ok_or_else(|| { - golem_rust::agentic::custom_error(format!( - "Internal Error: Parameter schemas not found for agent: {}, method index: {}", - #agent_type_name, #sorted_method_index - )) - })?; + let mut input_param_index: usize = 0; + let __agent_type_name = golem_rust::agentic::AgentTypeName(#agent_type_name.to_string()); + let __param_schemas = golem_rust::agentic::get_method_parameter_types_by_index( + &__agent_type_name, + #sorted_method_index + ).ok_or_else(|| { + golem_rust::agentic::custom_error(format!( + "Internal Error: Parameter schemas not found for agent: {}, method index: {}", + #agent_type_name, #sorted_method_index + )) + })?; }; - let extraction: Vec = param_idents.iter().enumerate().map(|(original_method_param_idx, ident)| { + let tuple_extraction: Vec = param_idents_and_types.iter().map(|(ident, pat_type)| { let ident_result = format_ident!("{}_result", ident); - quote! { - let #ident_result = match &mut __input_variant { - __InputVariant::Tuple(values) => { - let enriched_schema = __param_schemas.get(#original_method_param_idx) + + if has_principal_attr(pat_type) { + quote! { + let #ident = principal.clone(); + } + } else { + quote! { + let #ident_result = { + let element_schema = __param_schemas.get(input_param_index) .cloned() .ok_or_else(|| { golem_rust::agentic::custom_error(format!( "Internal Error: Parameter schema not found for agent: {}, method: {}, parameter index: {}", - #agent_type_name, #method_name, #original_method_param_idx + #agent_type_name, #method_name, input_param_index )) })?; - match enriched_schema { - golem_rust::agentic::EnrichedElementSchema::AutoInject(auto_injected_schema) => { - match auto_injected_schema { - golem_rust::agentic::AutoInjectedParamType::Principal => { - golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::AutoInjected(golem_rust::agentic::AutoInjectedValue::Principal(principal.clone())), golem_rust::agentic::StructuredSchema::AutoInject(golem_rust::agentic::AutoInjectedParamType::Principal)).map_err(|e| { - golem_rust::agentic::invalid_input_error(format!("Failed parsing arg {} for method {}: {}", #original_method_param_idx, #method_name, e)) - }) - } - } - } + let element_value = if input_param_index < values.len() { + values[input_param_index].take().ok_or_else(|| { + golem_rust::agentic::invalid_input_error(format!( + "Argument already consumed in method {}", + #method_name + )) + })? + } else { + return Err(golem_rust::agentic::invalid_input_error(format!( + "Missing arguments in method {}", + #method_name + ))); + }; + + input_param_index += 1; + + golem_rust::agentic::Schema::from_structured_value( + golem_rust::agentic::StructuredValue::Default(element_value), + golem_rust::agentic::StructuredSchema::Default(element_schema), + ).map_err(|e| { + golem_rust::agentic::invalid_input_error(format!( + "Failed parsing arg {} for method {}: {}", + input_param_index, #method_name, e + )) + }) + }; + + let #ident = #ident_result?; + } + } + }).collect(); - golem_rust::agentic::EnrichedElementSchema::ElementSchema(element_schema) => { - let element_value = if input_param_index < values.len() { - values[input_param_index].take().ok_or_else(|| { - golem_rust::agentic::invalid_input_error(format!("Argument already consumed in method {}", #method_name)) - })? - } else { - return Err(golem_rust::agentic::invalid_input_error(format!("Missing arguments in method {}", #method_name))); - }; - - // only increment the input_param_index for non auto-injected parameters - input_param_index += 1; - - golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::Default(element_value), golem_rust::agentic::StructuredSchema::Default(element_schema)).map_err(|e| { - golem_rust::agentic::invalid_input_error(format!("Failed parsing arg {} for method {}: {}", #original_method_param_idx, #method_name, e)) - }) - } - } - }, - __InputVariant::Multimodal(elements) => { - let deserialized_value = golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::Multimodal(elements.take().unwrap_or_default()), golem_rust::agentic::StructuredSchema::Multimodal(vec![])).map_err(|e| { - golem_rust::agentic::invalid_input_error(format!("Failed parsing arg {} for method {}: {}", #original_method_param_idx, #method_name, e)) - })?; - Ok(deserialized_value) + let multimodal_extraction: Vec = param_idents_and_types.iter().map(|(ident, pat_type)| { + let ident_result = format_ident!("{}_result", ident); + + if has_principal_attr(pat_type) { + quote! { + let #ident = principal.clone(); + } + } else { + quote! { + let #ident_result = { + let deserialized_value = + golem_rust::agentic::Schema::from_structured_value( + golem_rust::agentic::StructuredValue::Multimodal(::std::mem::take(&mut elements)), + golem_rust::agentic::StructuredSchema::Multimodal(vec![]), + ).map_err(|e| { + golem_rust::agentic::invalid_input_error(format!( + "Failed parsing arg {} for method {}: {}", + input_param_index, #method_name, e + )) + })?; - } - }; + Ok(deserialized_value) + }; - let #ident = #ident_result?; + let #ident = #ident_result?; + } } }).collect(); quote! { - enum __InputVariant { - Tuple(Vec>), - Multimodal(Option>), - } + #input_param_index_init - let mut __input_variant = match input { + match input { golem_rust::golem_agentic::golem::agent::common::DataValue::Tuple(values) => { - __InputVariant::Tuple(values.into_iter().map(Some).collect()) - }, - golem_rust::golem_agentic::golem::agent::common::DataValue::Multimodal(elements) => { - __InputVariant::Multimodal(Some(elements)) - }, - }; + let mut values: Vec> = values.into_iter().map(Some).collect(); + #(#tuple_extraction)* + #post_method_param_extraction_logic + } - #input_param_index_init - #(#extraction)* - #post_method_param_extraction_logic + golem_rust::golem_agentic::golem::agent::common::DataValue::Multimodal(mut elements) => { + #(#multimodal_extraction)* + #post_method_param_extraction_logic + } + } } } @@ -509,20 +519,23 @@ fn generate_constructor_extraction( let mut schema_param_index: usize = 0; for (ident, pat_type) in ctor_params { + let ty = &pat_type.ty; if has_agent_config_attr(pat_type) { - let ty = &pat_type.ty; config_extractions.push(quote! { let #ident: #ty = ::golem_rust::agentic::Config::new(); }); + } else if has_principal_attr(pat_type) { + config_extractions.push(quote! { + let #ident: #ty = principal.clone(); + }); } else { - let ty = &pat_type.ty; let idx = schema_param_index; predecls.push(quote! { let #ident: #ty; }); tuple_extractions.push(quote! { { - let enriched_schema = __ctor_schemas.get(#idx) + let element_schema = __ctor_schemas.get(#idx) .cloned() .ok_or_else(|| { golem_rust::agentic::internal_error(format!( @@ -531,33 +544,19 @@ fn generate_constructor_extraction( )) })?; - match enriched_schema { - golem_rust::agentic::EnrichedElementSchema::AutoInject(auto_injected_schema) => { - match auto_injected_schema { - golem_rust::agentic::AutoInjectedParamType::Principal => { - #ident = golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::AutoInjected(golem_rust::agentic::AutoInjectedValue::Principal(principal.clone())), golem_rust::agentic::StructuredSchema::AutoInject(golem_rust::agentic::AutoInjectedParamType::Principal)).map_err(|e| { - golem_rust::agentic::invalid_input_error(format!("Failed parsing constructor arg {}: {}", #idx, e)) - })?; - } - } - } - - golem_rust::agentic::EnrichedElementSchema::ElementSchema(element_schema) => { - let element_value = if input_param_index < values.len() { - values[input_param_index].take().ok_or_else(|| { - golem_rust::agentic::invalid_input_error(format!("Constructor argument already consumed for agent {}", #agent_type_name)) - })? - } else { - return Err(golem_rust::agentic::invalid_input_error(format!("Missing constructor arguments for agent {}", #agent_type_name))); - }; + let element_value = if input_param_index < values.len() { + values[input_param_index].take().ok_or_else(|| { + golem_rust::agentic::invalid_input_error(format!("Constructor argument already consumed for agent {}", #agent_type_name)) + })? + } else { + return Err(golem_rust::agentic::invalid_input_error(format!("Missing constructor arguments for agent {}", #agent_type_name))); + }; - input_param_index += 1; + input_param_index += 1; - #ident = golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::Default(element_value), golem_rust::agentic::StructuredSchema::Default(element_schema)).map_err(|e| { - golem_rust::agentic::invalid_input_error(format!("Failed parsing constructor arg {}: {}", #idx, e)) - })?; - } - } + #ident = golem_rust::agentic::Schema::from_structured_value(golem_rust::agentic::StructuredValue::Default(element_value), golem_rust::agentic::StructuredSchema::Default(element_schema)).map_err(|e| { + golem_rust::agentic::invalid_input_error(format!("Failed parsing constructor arg {}: {}", #idx, e)) + })?; } }); schema_param_index += 1; diff --git a/sdks/rust/golem-rust-macro/src/agentic/client_generation.rs b/sdks/rust/golem-rust-macro/src/agentic/client_generation.rs index e55fb46664..a038417032 100644 --- a/sdks/rust/golem-rust-macro/src/agentic/client_generation.rs +++ b/sdks/rust/golem-rust-macro/src/agentic/client_generation.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::agentic::helpers::{FunctionOutputInfo, is_static_method}; +use crate::agentic::helpers::{FunctionOutputInfo, has_principal_attr, is_static_method}; use crate::agentic::{generic_type_in_agent_method_error, generic_type_in_agent_return_type_error}; use quote::{format_ident, quote}; use syn::spanned::Spanned; @@ -264,8 +264,8 @@ fn get_remote_agent_methods_info( return Some(ts); } - let input_defs = collect_input_defs_without_principal(&method.sig); - let input_idents = collect_input_idents_without_principal(&method.sig); + let input_defs = collect_input_defs(&method.sig); + let input_idents = collect_input_idents(&method.sig); let method_name = &method.sig.ident; let trigger_name = format_ident!("trigger_{}", method_name); @@ -346,32 +346,24 @@ fn validate_input_types( Ok(()) } -fn collect_input_defs_without_principal(sig: &syn::Signature) -> Vec<&syn::FnArg> { - sig.inputs.iter().filter(|arg| match arg { - FnArg::Receiver(_) => true, - FnArg::Typed(pat_type) => !matches!( - &*pat_type.ty, - Type::Path(type_path) if type_path.path.segments.last().map(|s| s.ident == "Principal").unwrap_or(false) - ), - }).collect() +fn collect_input_defs(sig: &syn::Signature) -> Vec<&syn::FnArg> { + sig.inputs + .iter() + .filter(|arg| match arg { + FnArg::Receiver(_) => true, + FnArg::Typed(pat_type) => !has_principal_attr(pat_type), + }) + .collect() } -fn collect_input_idents_without_principal(sig: &syn::Signature) -> Vec { +fn collect_input_idents(sig: &syn::Signature) -> Vec { sig.inputs .iter() .filter_map(|arg| { if let FnArg::Typed(pat_type) = arg + && !has_principal_attr(pat_type) && let syn::Pat::Ident(pat_ident) = &*pat_type.pat { - if let Type::Path(type_path) = &*pat_type.ty - && type_path - .path - .segments - .last() - .is_some_and(|seg| seg.ident == "Principal") - { - return None; - } return Some(pat_ident.ident.clone()); } None diff --git a/sdks/rust/golem-rust-macro/src/agentic/helpers.rs b/sdks/rust/golem-rust-macro/src/agentic/helpers.rs index 157c8a07a5..7604604e34 100644 --- a/sdks/rust/golem-rust-macro/src/agentic/helpers.rs +++ b/sdks/rust/golem-rust-macro/src/agentic/helpers.rs @@ -101,11 +101,20 @@ pub fn has_agent_config_attr(pat_type: &syn::PatType) -> bool { .any(|a| a.path().is_ident("agent_config")) } -pub struct AgentConfigAttrRemover; -impl VisitMut for AgentConfigAttrRemover { +pub fn has_principal_attr(pat_type: &syn::PatType) -> bool { + pat_type + .attrs + .iter() + .any(|a| a.path().is_ident("principal")) +} + +pub struct GolemAgentAttrsRemover; +impl VisitMut for GolemAgentAttrsRemover { fn visit_fn_arg_mut(&mut self, i: &mut syn::FnArg) { if let syn::FnArg::Typed(arg) = i { - arg.attrs.retain(|att| !att.path().is_ident("agent_config")); + arg.attrs.retain(|att| { + !att.path().is_ident("agent_config") && !att.path().is_ident("principal") + }); } syn::visit_mut::visit_fn_arg_mut(self, i); } diff --git a/sdks/rust/golem-rust/benches/agent_registry_bench.rs b/sdks/rust/golem-rust/benches/agent_registry_bench.rs index 4c0da4ac34..93cd1d1e9a 100644 --- a/sdks/rust/golem-rust/benches/agent_registry_bench.rs +++ b/sdks/rust/golem-rust/benches/agent_registry_bench.rs @@ -25,12 +25,13 @@ test_r::enable!(); #[cfg(feature = "export_golem_agentic")] mod bench { use golem_rust::agentic::{ - AgentTypeName, EnrichedAgentMethod, EnrichedElementSchema, ExtendedAgentConstructor, - ExtendedAgentType, ExtendedDataSchema, get_constructor_parameter_type, - get_enriched_agent_type_by_name, get_method_parameter_type, - get_method_parameter_types_by_index, register_agent_type, + AgentTypeName, get_agent_type_by_name, get_constructor_parameter_type, + get_method_parameter_type, get_method_parameter_types_by_index, register_agent_type, + }; + use golem_rust::golem_agentic::golem::agent::common::{ + AgentConstructor, AgentMethod, AgentMode, AgentType, DataSchema, ElementSchema, + Snapshotting, }; - use golem_rust::golem_agentic::golem::agent::common::{AgentMode, ElementSchema, Snapshotting}; use golem_wasm::golem_core_1_5_x::types::{NamedWitTypeNode, WitType, WitTypeNode}; use std::hint::black_box; use std::time::Instant; @@ -46,52 +47,39 @@ mod bench { }) } - fn make_method(name: &str, param_count: usize) -> EnrichedAgentMethod { - let params: Vec<(String, EnrichedElementSchema)> = (0..param_count) - .map(|i| { - ( - format!("param_{}", i), - EnrichedElementSchema::ElementSchema(make_element_schema()), - ) - }) + fn make_method(name: &str, param_count: usize) -> AgentMethod { + let params: Vec<(String, ElementSchema)> = (0..param_count) + .map(|i| (format!("param_{}", i), make_element_schema())) .collect(); - EnrichedAgentMethod { + AgentMethod { name: name.to_string(), description: format!("Method {}", name), http_endpoint: vec![], prompt_hint: Some("hint".to_string()), - input_schema: ExtendedDataSchema::Tuple(params), - output_schema: ExtendedDataSchema::Tuple(vec![( - "result".to_string(), - EnrichedElementSchema::ElementSchema(make_element_schema()), - )]), + input_schema: DataSchema::Tuple(params), + output_schema: DataSchema::Tuple(vec![("result".to_string(), make_element_schema())]), } } fn register_test_agent(name: &str, method_count: usize, params_per_method: usize) { - let methods: Vec = (0..method_count) + let methods: Vec = (0..method_count) .map(|i| make_method(&format!("method_{}", i), params_per_method)) .collect(); - let constructor_params: Vec<(String, EnrichedElementSchema)> = (0..params_per_method) - .map(|i| { - ( - format!("ctor_param_{}", i), - EnrichedElementSchema::ElementSchema(make_element_schema()), - ) - }) + let constructor_params: Vec<(String, ElementSchema)> = (0..params_per_method) + .map(|i| (format!("ctor_param_{}", i), make_element_schema())) .collect(); - let agent_type = ExtendedAgentType { + let agent_type = AgentType { type_name: name.to_string(), description: "Benchmark test agent".to_string(), source_language: "rust".to_string(), - constructor: ExtendedAgentConstructor { + constructor: AgentConstructor { name: Some("new".to_string()), description: "Constructor".to_string(), prompt_hint: None, - input_schema: ExtendedDataSchema::Tuple(constructor_params), + input_schema: DataSchema::Tuple(constructor_params), }, methods, dependencies: vec![], @@ -99,7 +87,6 @@ mod bench { http_mount: None, snapshotting: Snapshotting::Disabled, config: vec![], - sorted_method_indices: vec![], }; register_agent_type(AgentTypeName(name.to_string()), agent_type); @@ -211,10 +198,10 @@ mod bench { ); // --- get_enriched_agent_type_by_name (full clone, for comparison) --- - println!("\n--- get_enriched_agent_type_by_name (full deep clone, for comparison) ---"); + println!("\n--- get_agent_type_by_name (full deep clone, for comparison) ---"); - bench_loop("full ExtendedAgentType clone", ITERATIONS, || { - black_box(get_enriched_agent_type_by_name(&agent_type_name)); + bench_loop("full AgentType clone", ITERATIONS, || { + black_box(get_agent_type_by_name(&agent_type_name)); }); println!(); diff --git a/sdks/rust/golem-rust/src/agentic/agent_registry.rs b/sdks/rust/golem-rust/src/agentic/agent_registry.rs index b00436c5ef..124b2b1dc9 100644 --- a/sdks/rust/golem-rust/src/agentic/agent_registry.rs +++ b/sdks/rust/golem-rust/src/agentic/agent_registry.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::agentic::extended_agent_type::ExtendedAgentType; -use crate::agentic::{EnrichedElementSchema, ExtendedDataSchema, Principal}; +use crate::agentic::Principal; +use crate::golem_agentic::golem::agent::common::{DataSchema, ElementSchema}; use crate::{ agentic::{ResolvedAgent, agent_initiator::AgentInitiator}, golem_agentic::exports::golem::agent::guest::AgentType, @@ -68,27 +68,18 @@ pub fn get_all_agent_types() -> Vec { .borrow() .agent_types .values() - .map(|e| e.to_agent_type()) + .map(ExtendedAgentType::to_agent_type) .collect() } -pub fn get_enriched_agent_type_by_name( - agent_type_name: &AgentTypeName, -) -> Option { +pub fn get_agent_type_by_name(agent_type_name: &AgentTypeName) -> Option { let state = get_state(); - state .agent_types .borrow() .agent_types .get(agent_type_name) - .cloned() -} - -pub fn get_agent_type_by_name(agent_type_name: &AgentTypeName) -> Option { - let enriched = get_enriched_agent_type_by_name(agent_type_name); - - enriched.map(|e| e.to_agent_type()) + .map(ExtendedAgentType::to_agent_type) } pub fn register_principal(principal: &Principal) { @@ -103,16 +94,12 @@ pub fn get_principal() -> Option { state.initialized_principal.borrow().clone() } -pub fn register_agent_type(agent_type_name: AgentTypeName, mut agent_type: ExtendedAgentType) { - let mut indices: Vec = (0..agent_type.methods.len()).collect(); - indices.sort_by(|&a, &b| agent_type.methods[a].name.cmp(&agent_type.methods[b].name)); - agent_type.sorted_method_indices = indices; - +pub fn register_agent_type(agent_type_name: AgentTypeName, agent_type: AgentType) { get_state() .agent_types .borrow_mut() .agent_types - .insert(agent_type_name, agent_type); + .insert(agent_type_name, ExtendedAgentType::new(agent_type)); } pub fn register_agent_initiator(agent_type_name: &str, initiator: Arc) { @@ -186,48 +173,56 @@ pub fn get_resolved_agent() -> Option> { pub fn get_constructor_parameter_type( agent_type_name: &AgentTypeName, parameter_index: usize, -) -> Option { +) -> Option { let state = get_state(); let agent_types = state.agent_types.borrow(); let agent_type = agent_types.agent_types.get(agent_type_name.0.as_str())?; - extract_parameter_schema(&agent_type.constructor.input_schema, parameter_index) + extract_parameter_schema(&agent_type.inner.constructor.input_schema, parameter_index) } pub fn get_method_parameter_type( agent_type_name: &AgentTypeName, method_name: &str, parameter_index: usize, -) -> Option { +) -> Option { let state = get_state(); let agent_types = state.agent_types.borrow(); let agent_type = agent_types.agent_types.get(agent_type_name.0.as_str())?; - let method = agent_type.methods.iter().find(|m| m.name == method_name)?; + let method = agent_type + .inner + .methods + .iter() + .find(|m| m.name == method_name)?; extract_parameter_schema(&method.input_schema, parameter_index) } pub fn get_constructor_parameter_types( agent_type_name: &AgentTypeName, -) -> Option> { +) -> Option> { let state = get_state(); let agent_types = state.agent_types.borrow(); let agent_type = agent_types.agent_types.get(agent_type_name.0.as_str())?; Some(extract_all_parameter_schemas( - &agent_type.constructor.input_schema, + &agent_type.inner.constructor.input_schema, )) } pub fn get_method_parameter_types( agent_type_name: &AgentTypeName, method_name: &str, -) -> Option> { +) -> Option> { let state = get_state(); let agent_types = state.agent_types.borrow(); let agent_type = agent_types.agent_types.get(agent_type_name.0.as_str())?; - let method = agent_type.methods.iter().find(|m| m.name == method_name)?; + let method = agent_type + .inner + .methods + .iter() + .find(|m| m.name == method_name)?; Some(extract_all_parameter_schemas(&method.input_schema)) } @@ -235,39 +230,31 @@ pub fn get_method_parameter_types( pub fn get_method_parameter_types_by_index( agent_type_name: &AgentTypeName, sorted_method_index: usize, -) -> Option> { +) -> Option> { let state = get_state(); let agent_types = state.agent_types.borrow(); let agent_type = agent_types.agent_types.get(agent_type_name.0.as_str())?; let orig_idx = *agent_type.sorted_method_indices.get(sorted_method_index)?; - let method = agent_type.methods.get(orig_idx)?; + let method = agent_type.inner.methods.get(orig_idx)?; Some(extract_all_parameter_schemas(&method.input_schema)) } -fn extract_all_parameter_schemas(schema: &ExtendedDataSchema) -> Vec { +fn extract_all_parameter_schemas(schema: &DataSchema) -> Vec { match schema { - ExtendedDataSchema::Tuple(items) => items.iter().map(|(_, s)| s.clone()).collect(), - ExtendedDataSchema::Multimodal(items) => items - .iter() - .map(|(_, s)| EnrichedElementSchema::ElementSchema(s.clone())) - .collect(), + DataSchema::Tuple(items) => items.iter().map(|(_, s)| s.clone()).collect(), + DataSchema::Multimodal(items) => items.iter().map(|(_, s)| s.clone()).collect(), } } -fn extract_parameter_schema( - schema: &ExtendedDataSchema, - parameter_index: usize, -) -> Option { +fn extract_parameter_schema(schema: &DataSchema, parameter_index: usize) -> Option { match schema { - ExtendedDataSchema::Tuple(items) => items + DataSchema::Tuple(items) => items + .get(parameter_index) + .map(|(_, element_schema)| element_schema.clone()), + DataSchema::Multimodal(items) => items .get(parameter_index) .map(|(_, element_schema)| element_schema.clone()), - ExtendedDataSchema::Multimodal(items) => { - items.get(parameter_index).map(|(_, element_schema)| { - EnrichedElementSchema::ElementSchema(element_schema.clone()) - }) - } } } @@ -302,3 +289,25 @@ impl std::borrow::Borrow for AgentTypeName { &self.0 } } + +pub struct ExtendedAgentType { + pub inner: AgentType, + pub sorted_method_indices: Vec, +} + +impl ExtendedAgentType { + pub fn new(agent_type: AgentType) -> Self { + let mut sorted_method_indices: Vec = (0..agent_type.methods.len()).collect(); + sorted_method_indices + .sort_by(|&a, &b| agent_type.methods[a].name.cmp(&agent_type.methods[b].name)); + + Self { + inner: agent_type, + sorted_method_indices, + } + } + + pub fn to_agent_type(&self) -> AgentType { + self.inner.clone() + } +} diff --git a/sdks/rust/golem-rust/src/agentic/extended_agent_type.rs b/sdks/rust/golem-rust/src/agentic/extended_agent_type.rs deleted file mode 100644 index ac951094d6..0000000000 --- a/sdks/rust/golem-rust/src/agentic/extended_agent_type.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2024-2026 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::agentic::AutoInjectedParamType; -use crate::golem_agentic::golem::agent::common::AgentConfigDeclaration; -use crate::golem_agentic::golem::agent::common::{ - AgentConstructor, AgentDependency, AgentMethod, AgentMode, AgentType, DataSchema, - ElementSchema, HttpEndpointDetails, HttpMountDetails, Snapshotting, -}; -use std::collections::HashSet; - -// An enriched representation is a different view of the agent type that will include -// extra details such as auto-injected schemas such as Principal -// The agent registry will hold this information for the generated `initiate` , `invoke` and rpc calls -// to look up so that these parameters can be injected automatically by the platform -#[derive(Clone)] -pub struct ExtendedAgentType { - pub type_name: String, - pub description: String, - pub source_language: String, - pub constructor: ExtendedAgentConstructor, - pub methods: Vec, - pub dependencies: Vec, - pub mode: AgentMode, - pub http_mount: Option, - pub snapshotting: Snapshotting, - pub config: Vec, - /// Maps from a name-sorted method index to the original index in `methods`. - /// Built at registration time to allow O(1) index-based lookup on the hot path - /// while preserving user-declared method order in `methods`. - pub sorted_method_indices: Vec, -} - -impl ExtendedAgentType { - pub fn principal_params_in_constructor(&self) -> HashSet { - let mut principal_params = HashSet::new(); - - if let ExtendedDataSchema::Tuple(fields) = &self.constructor.input_schema { - for (name, schema) in fields { - if let EnrichedElementSchema::AutoInject(AutoInjectedParamType::Principal) = schema - { - principal_params.insert(name.clone()); - } - } - } - - principal_params - } - - pub fn to_agent_type(&self) -> AgentType { - AgentType { - type_name: self.type_name.clone(), - description: self.description.clone(), - source_language: self.source_language.clone(), - constructor: self.constructor.to_agent_constructor(), - methods: self.methods.iter().map(|m| m.to_agent_method()).collect(), - dependencies: self.dependencies.clone(), - mode: self.mode, - http_mount: self.http_mount.clone(), - snapshotting: self.snapshotting, - config: self.config.clone(), - } - } -} - -#[derive(Clone)] -pub struct EnrichedAgentMethod { - pub name: String, - pub description: String, - pub http_endpoint: Vec, - pub prompt_hint: Option, - pub input_schema: ExtendedDataSchema, - pub output_schema: ExtendedDataSchema, -} - -impl EnrichedAgentMethod { - pub fn to_agent_method(&self) -> AgentMethod { - AgentMethod { - name: self.name.clone(), - description: self.description.clone(), - http_endpoint: self.http_endpoint.clone(), - prompt_hint: self.prompt_hint.clone(), - input_schema: self.input_schema.to_data_schema(), - output_schema: self.output_schema.to_data_schema(), - } - } -} - -#[derive(Clone)] -pub struct ExtendedAgentConstructor { - pub name: Option, - pub description: String, - pub prompt_hint: Option, - pub input_schema: ExtendedDataSchema, -} - -impl ExtendedAgentConstructor { - pub fn to_agent_constructor(&self) -> AgentConstructor { - AgentConstructor { - name: self.name.clone(), - description: self.description.clone(), - prompt_hint: self.prompt_hint.clone(), - input_schema: self.input_schema.to_data_schema(), - } - } -} - -#[derive(Clone)] -pub enum ExtendedDataSchema { - Tuple(Vec<(String, EnrichedElementSchema)>), - // Disallow any auto-injected schemas within multimodal - // This simplifies the implementation as multimodal is not expected to have anything outside - // such as Principal - Multimodal(Vec<(String, ElementSchema)>), -} - -impl ExtendedDataSchema { - pub fn to_data_schema(&self) -> DataSchema { - match self { - ExtendedDataSchema::Tuple(fields) => { - let fields_without_auto_injected = fields - .iter() - .filter_map(|(name, schema)| match schema { - EnrichedElementSchema::ElementSchema(element_schema) => { - Some((name.clone(), element_schema.clone())) - } - EnrichedElementSchema::AutoInject(_) => None, - }) - .collect::>(); - - DataSchema::Tuple(fields_without_auto_injected) - } - ExtendedDataSchema::Multimodal(variants) => DataSchema::Multimodal(variants.clone()), - } - } -} - -#[derive(Clone)] -pub enum EnrichedElementSchema { - ElementSchema(ElementSchema), - AutoInject(AutoInjectedParamType), -} diff --git a/sdks/rust/golem-rust/src/agentic/http/validations.rs b/sdks/rust/golem-rust/src/agentic/http/validations.rs index c7b3342d74..5dce2e6259 100644 --- a/sdks/rust/golem-rust/src/agentic/http/validations.rs +++ b/sdks/rust/golem-rust/src/agentic/http/validations.rs @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::agentic::{ - AutoInjectedParamType, EnrichedAgentMethod, EnrichedElementSchema, ExtendedDataSchema, -}; use crate::golem_agentic::golem::agent::common::{ - AgentConstructor, DataSchema, ElementSchema, HttpEndpointDetails, HttpMountDetails, PathSegment, + AgentConstructor, AgentMethod, DataSchema, ElementSchema, HttpEndpointDetails, + HttpMountDetails, PathSegment, }; use std::collections::HashSet; @@ -26,13 +24,11 @@ pub fn validate_http_mount( agent_class_name: &str, agent_mount: &HttpMountDetails, agent_constructor: &AgentConstructor, - parameters_for_principal: &HashSet, ) -> Result<(), String> { let constructor_input_params = collect_constructor_input_parameter_names(agent_constructor); validate_no_catch_all_in_http_mount(agent_class_name, agent_mount)?; validate_constructor_params_are_http_safe(agent_class_name, agent_constructor)?; - validate_mount_variables_are_not_principal(agent_mount, parameters_for_principal)?; validate_mount_variables_exist_in_constructor(agent_mount, &constructor_input_params)?; validate_constructor_vars_are_satisfied(agent_mount, &constructor_input_params)?; @@ -41,7 +37,7 @@ pub fn validate_http_mount( pub fn validate_http_endpoint( agent_class_name: &str, - agent_method: &EnrichedAgentMethod, + agent_method: &AgentMethod, http_mount_details: Option<&HttpMountDetails>, ) -> Result<(), String> { if agent_method.http_endpoint.is_empty() { @@ -57,15 +53,12 @@ pub fn validate_http_endpoint( let method_vars_without_auto_injected_variables = collect_method_input_vars(&agent_method.input_schema); - let principal_params = collect_principal_parameter_names(&agent_method.input_schema); - let unstructured_binary_params = collect_unstructured_binary_params(&agent_method.input_schema); for endpoint in &agent_method.http_endpoint { validate_endpoint_variables( endpoint, &method_vars_without_auto_injected_variables, - &principal_params, &unstructured_binary_params, )?; } @@ -73,60 +66,34 @@ pub fn validate_http_endpoint( Ok(()) } -fn collect_principal_parameter_names(input_schema: &ExtendedDataSchema) -> HashSet { - let mut principal_params = HashSet::new(); - - match input_schema { - ExtendedDataSchema::Tuple(name_and_schemas) => { - for (param_name, param_schema) in name_and_schemas { - if let EnrichedElementSchema::AutoInject(auto_injected_schema) = param_schema { - match auto_injected_schema { - AutoInjectedParamType::Principal => { - principal_params.insert(param_name.clone()); - } - } - } - } - } - ExtendedDataSchema::Multimodal(_) => {} - } - - principal_params -} - -fn collect_unstructured_binary_params(input_schema: &ExtendedDataSchema) -> HashSet { +fn collect_unstructured_binary_params(input_schema: &DataSchema) -> HashSet { let mut unstructured_binary_params = HashSet::new(); match input_schema { - ExtendedDataSchema::Tuple(name_and_schemas) => { + DataSchema::Tuple(name_and_schemas) => { for (param_name, param_schema) in name_and_schemas { - if let EnrichedElementSchema::ElementSchema(ElementSchema::UnstructuredBinary(_)) = - param_schema - { + if let ElementSchema::UnstructuredBinary(_) = param_schema { unstructured_binary_params.insert(param_name.clone()); } } } - ExtendedDataSchema::Multimodal(_) => {} + DataSchema::Multimodal(_) => {} } unstructured_binary_params } // Collects method input variable names, excluding auto-injected variables. -fn collect_method_input_vars(input_schema: &ExtendedDataSchema) -> HashSet { +fn collect_method_input_vars(input_schema: &DataSchema) -> HashSet { let mut param_names = HashSet::new(); match input_schema { - ExtendedDataSchema::Tuple(name_and_schemas) => { - for (param_name, param_schema) in name_and_schemas { - if let EnrichedElementSchema::AutoInject(_) = param_schema { - continue; - } + DataSchema::Tuple(name_and_schemas) => { + for (param_name, _) in name_and_schemas { param_names.insert(param_name.clone()); } } - ExtendedDataSchema::Multimodal(_) => {} + DataSchema::Multimodal(_) => {} } param_names @@ -135,24 +102,15 @@ fn collect_method_input_vars(input_schema: &ExtendedDataSchema) -> HashSet, - principal_params: &HashSet, unstructured_binary_params: &HashSet, ) -> Result<(), String> { fn validate_variable( variable_name: &str, location: &str, - principal_params: &HashSet, unstructured_binary_params: &HashSet, method_vars: &HashSet, binary_error: &str, ) -> Result<(), String> { - if principal_params.contains(variable_name) { - return Err(format!( - "HTTP endpoint {} variable '{}' cannot be used for parameters of type 'Principal'", - location, variable_name - )); - } - if unstructured_binary_params.contains(variable_name) { return Err(binary_error.to_string()); } @@ -171,7 +129,6 @@ fn validate_endpoint_variables( validate_variable( &var.variable_name, "header", - principal_params, unstructured_binary_params, method_vars, &format!( @@ -185,7 +142,6 @@ fn validate_endpoint_variables( validate_variable( &var.variable_name, "query", - principal_params, unstructured_binary_params, method_vars, &format!( @@ -203,7 +159,6 @@ fn validate_endpoint_variables( validate_variable( name, "path", - principal_params, unstructured_binary_params, method_vars, &format!( @@ -222,7 +177,7 @@ fn validate_endpoint_variables( fn validate_mount_is_defined_for_http_endpoint( agent_class_name: &str, - agent_method: &EnrichedAgentMethod, + agent_method: &AgentMethod, http_mount_details: Option<&HttpMountDetails>, ) -> Result<(), String> { if http_mount_details.is_none() && !agent_method.http_endpoint.is_empty() { @@ -329,26 +284,6 @@ fn validate_constructor_params_are_http_safe( Ok(()) } -fn validate_mount_variables_are_not_principal( - agent_mount: &HttpMountDetails, - parameters_for_principal: &HashSet, -) -> Result<(), String> { - for segment in &agent_mount.path_prefix { - if let PathSegment::PathVariable(variable) = segment { - let variable_name = &variable.variable_name; - - if parameters_for_principal.contains(variable_name) { - return Err(format!( - "HTTP mount path variable '{}' cannot be used for constructor parameters of type 'Principal'", - variable_name, - )); - } - } - } - - Ok(()) -} - fn validate_mount_variables_exist_in_constructor( agent_mount: &HttpMountDetails, constructor_vars: &std::collections::HashSet, @@ -390,8 +325,6 @@ fn validate_constructor_vars_are_satisfied( #[cfg(test)] mod tests { - use test_r::test; - use super::*; use crate::agentic::{Schema, StructuredSchema}; use crate::golem_agentic::golem::agent::common::{ @@ -400,11 +333,8 @@ mod tests { }; use golem_rust_macro::AllowedMimeTypes; use golem_wasm::agentic::unstructured_binary::UnstructuredBinary; - use std::collections::HashSet; - fn principal_params(names: &[&str]) -> HashSet { - names.iter().map(|s| s.to_string()).collect() - } + use test_r::test; fn constructor_with_params(params: Vec<(&str, StructuredSchema)>) -> AgentConstructor { let data_schema = DataSchema::Tuple( @@ -416,9 +346,6 @@ mod tests { StructuredSchema::Multimodal(_) => { panic!("Multimodal schema not supported in this test constructor") } - StructuredSchema::AutoInject(_) => { - panic!("AutoInjected schema not supported in this test constructor") - } }; (name.to_string(), element_schema) @@ -471,9 +398,7 @@ mod tests { let mount = mount_with_segments(vec![literal(), path_var("user_id"), path_var("org_id")]); - let principal_params = principal_params(&[]); - - let result = validate_http_mount("MyAgent", &mount, &constructor, &principal_params); + let result = validate_http_mount("MyAgent", &mount, &constructor); assert!(result.is_ok()); } @@ -484,8 +409,7 @@ mod tests { let mount = mount_with_segments(vec![path_var("id"), catch_all("rest")]); - let err = - validate_http_mount("MyAgent", &mount, &constructor, &HashSet::new()).unwrap_err(); + let err = validate_http_mount("MyAgent", &mount, &constructor).unwrap_err(); assert_eq!( err, @@ -507,8 +431,7 @@ mod tests { let mount = mount_with_segments(vec![path_var("blob")]); - let err = - validate_http_mount("MyAgent", &mount, &constructor, &HashSet::new()).unwrap_err(); + let err = validate_http_mount("MyAgent", &mount, &constructor).unwrap_err(); assert_eq!( err, @@ -516,31 +439,13 @@ mod tests { ); } - #[test] - fn fails_when_mount_variable_is_principal() { - let constructor = constructor_with_params(vec![("user", String::get_type())]); - - let mount = mount_with_segments(vec![path_var("user")]); - - let principal_params = principal_params(&["user"]); - - let err = - validate_http_mount("MyAgent", &mount, &constructor, &principal_params).unwrap_err(); - - assert_eq!( - err, - "HTTP mount path variable 'user' cannot be used for constructor parameters of type 'Principal'" - ); - } - #[test] fn fails_when_mount_variable_not_in_constructor() { let constructor = constructor_with_params(vec![("id", String::get_type())]); let mount = mount_with_segments(vec![path_var("missing")]); - let err = - validate_http_mount("MyAgent", &mount, &constructor, &HashSet::new()).unwrap_err(); + let err = validate_http_mount("MyAgent", &mount, &constructor).unwrap_err(); assert_eq!( err, @@ -557,8 +462,7 @@ mod tests { let mount = mount_with_segments(vec![path_var("id")]); - let err = - validate_http_mount("MyAgent", &mount, &constructor, &HashSet::new()).unwrap_err(); + let err = validate_http_mount("MyAgent", &mount, &constructor).unwrap_err(); assert_eq!( err, @@ -566,39 +470,24 @@ mod tests { ); } - fn make_schema( - normal_vars: Vec<&str>, - principal_vars: Vec<&str>, - unstructured_vars: Vec<&str>, - ) -> ExtendedDataSchema { + fn make_schema(normal_vars: Vec<&str>, unstructured_vars: Vec<&str>) -> DataSchema { let mut fields = vec![]; for name in normal_vars { fields.push(( name.to_string(), - EnrichedElementSchema::ElementSchema( - String::get_type().get_element_schema().unwrap(), - ), - )); - } - - for name in principal_vars { - fields.push(( - name.to_string(), - EnrichedElementSchema::AutoInject(AutoInjectedParamType::Principal), + String::get_type().get_element_schema().unwrap(), )); } for name in unstructured_vars { fields.push(( name.to_string(), - EnrichedElementSchema::ElementSchema(ElementSchema::UnstructuredBinary( - BinaryDescriptor { restrictions: None }, - )), + ElementSchema::UnstructuredBinary(BinaryDescriptor { restrictions: None }), )); } - ExtendedDataSchema::Tuple(fields) + DataSchema::Tuple(fields) } fn make_endpoint( @@ -642,22 +531,22 @@ mod tests { fn make_agent_method( name: &str, - input_schema: ExtendedDataSchema, + input_schema: DataSchema, endpoints: Vec, - ) -> EnrichedAgentMethod { - EnrichedAgentMethod { + ) -> AgentMethod { + AgentMethod { name: name.to_string(), description: "".to_string(), prompt_hint: None, input_schema, - output_schema: ExtendedDataSchema::Tuple(vec![]), + output_schema: DataSchema::Tuple(vec![]), http_endpoint: endpoints, } } #[test] fn test_no_http_endpoints() { - let agent_method = make_agent_method("foo", make_schema(vec!["x"], vec![], vec![]), vec![]); + let agent_method = make_agent_method("foo", make_schema(vec!["x"], vec![]), vec![]); assert!(validate_http_endpoint("AgentA", &agent_method, None).is_ok()); } @@ -671,11 +560,7 @@ mod tests { Some(true), vec![], ); - let agent_method = make_agent_method( - "foo", - make_schema(vec!["x"], vec![], vec![]), - vec![endpoint], - ); + let agent_method = make_agent_method("foo", make_schema(vec!["x"], vec![]), vec![endpoint]); let err = validate_http_endpoint("AgentA", &agent_method, None).unwrap_err(); assert!(err.contains("defines HTTP endpoints but the agent is not mounted over HTTP")); } @@ -690,35 +575,11 @@ mod tests { Some(true), vec!["*"], ); - let agent_method = make_agent_method( - "foo", - make_schema(vec!["x"], vec![], vec![]), - vec![endpoint], - ); + let agent_method = make_agent_method("foo", make_schema(vec!["x"], vec![]), vec![endpoint]); let mount = mount_with_segments(vec![path_var("foo")]); assert!(validate_http_endpoint("AgentA", &agent_method, Some(&mount)).is_ok()); } - #[test] - fn test_header_principal_error() { - let endpoint = make_endpoint( - HttpMethod::Get, - vec![], - vec![("X-Test", "p")], - vec![], - Some(true), - vec![], - ); - let agent_method = make_agent_method( - "foo", - make_schema(vec![], vec!["p"], vec![]), - vec![endpoint], - ); - let mount = mount_with_segments(vec![path_var("foo")]); - let err = validate_http_endpoint("AgentA", &agent_method, Some(&mount)).unwrap_err(); - assert!(err.contains("cannot be used for parameters of type 'Principal'")); - } - #[test] fn test_header_unstructured_binary_error() { let endpoint = make_endpoint( @@ -729,11 +590,7 @@ mod tests { Some(true), vec![], ); - let agent_method = make_agent_method( - "foo", - make_schema(vec![], vec![], vec!["b"]), - vec![endpoint], - ); + let agent_method = make_agent_method("foo", make_schema(vec![], vec!["b"]), vec![endpoint]); let mount = mount_with_segments(vec![path_var("foo")]); let err = validate_http_endpoint("AgentA", &agent_method, Some(&mount)).unwrap_err(); assert!(err.contains("cannot be used for method parameters of type 'UnstructuredBinary'")); @@ -749,11 +606,7 @@ mod tests { Some(true), vec![], ); - let agent_method = make_agent_method( - "foo", - make_schema(vec!["x"], vec![], vec![]), - vec![endpoint], - ); + let agent_method = make_agent_method("foo", make_schema(vec!["x"], vec![]), vec![endpoint]); let mount = mount_with_segments(vec![path_var("foo")]); let err = validate_http_endpoint("AgentA", &agent_method, Some(&mount)).unwrap_err(); assert!(err.contains("is not defined in method input parameters")); diff --git a/sdks/rust/golem-rust/src/agentic/mod.rs b/sdks/rust/golem-rust/src/agentic/mod.rs index 058c4e6d9c..7ac843ddd5 100644 --- a/sdks/rust/golem-rust/src/agentic/mod.rs +++ b/sdks/rust/golem-rust/src/agentic/mod.rs @@ -19,7 +19,6 @@ pub use agent_initiator::*; pub use agent_registry::*; pub use async_utils::*; pub use errors::*; -pub use extended_agent_type::*; pub use golem_wasm::agentic::unstructured_binary::*; pub use golem_wasm::agentic::unstructured_text::*; pub use http::*; @@ -36,10 +35,9 @@ mod agent_initiator; mod agent_registry; mod async_utils; mod errors; -mod extended_agent_type; mod http; mod multimodal; -mod principal_serde; +mod principal; mod resolved_agent; mod schema; pub mod snapshot_auto; diff --git a/sdks/rust/golem-rust/src/agentic/principal.rs b/sdks/rust/golem-rust/src/agentic/principal.rs new file mode 100644 index 0000000000..218fd3f34a --- /dev/null +++ b/sdks/rust/golem-rust/src/agentic/principal.rs @@ -0,0 +1,459 @@ +// Copyright 2024-2026 Golem Cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::golem_agentic::golem::agent::common::{ + AgentPrincipal, GolemUserPrincipal, OidcPrincipal, Principal, +}; +use crate::value_and_type::{FromValueAndType, IntoValue, TypeNodeBuilder}; +use golem_wasm::golem_core_1_5_x::types::{AccountId, AgentId, ComponentId, Uuid}; +use golem_wasm::{NodeBuilder, WitValueExtractor}; +use serde::de::{self, MapAccess, Visitor}; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +fn uuid_to_string(uuid: &Uuid) -> String { + uuid::Uuid::from_u64_pair(uuid.high_bits, uuid.low_bits).to_string() +} + +fn uuid_from_string(s: &str) -> Result { + let parsed = uuid::Uuid::parse_str(s)?; + let (high_bits, low_bits) = parsed.as_u64_pair(); + Ok(Uuid { + high_bits, + low_bits, + }) +} + +impl Serialize for OidcPrincipal { + fn serialize(&self, serializer: S) -> Result { + let mut s = serializer.serialize_struct("OidcPrincipal", 10)?; + s.serialize_field("sub", &self.sub)?; + s.serialize_field("issuer", &self.issuer)?; + s.serialize_field("email", &self.email)?; + s.serialize_field("name", &self.name)?; + s.serialize_field("emailVerified", &self.email_verified)?; + s.serialize_field("givenName", &self.given_name)?; + s.serialize_field("familyName", &self.family_name)?; + s.serialize_field("picture", &self.picture)?; + s.serialize_field("preferredUsername", &self.preferred_username)?; + s.serialize_field("claims", &self.claims)?; + s.end() + } +} + +impl<'de> Deserialize<'de> for OidcPrincipal { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Helper { + sub: String, + issuer: String, + email: Option, + name: Option, + email_verified: Option, + given_name: Option, + family_name: Option, + picture: Option, + preferred_username: Option, + claims: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(OidcPrincipal { + sub: h.sub, + issuer: h.issuer, + email: h.email, + name: h.name, + email_verified: h.email_verified, + given_name: h.given_name, + family_name: h.family_name, + picture: h.picture, + preferred_username: h.preferred_username, + claims: h.claims, + }) + } +} + +impl Serialize for AgentPrincipal { + fn serialize(&self, serializer: S) -> Result { + let mut s = serializer.serialize_struct("AgentPrincipal", 2)?; + s.serialize_field( + "componentId", + &uuid_to_string(&self.agent_id.component_id.uuid), + )?; + s.serialize_field("agentId", &self.agent_id.agent_id)?; + s.end() + } +} + +impl<'de> Deserialize<'de> for AgentPrincipal { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Helper { + component_id: String, + agent_id: String, + } + let h = Helper::deserialize(deserializer)?; + let uuid = uuid_from_string(&h.component_id).map_err(de::Error::custom)?; + Ok(AgentPrincipal { + agent_id: AgentId { + component_id: ComponentId { uuid }, + agent_id: h.agent_id, + }, + }) + } +} + +impl Serialize for GolemUserPrincipal { + fn serialize(&self, serializer: S) -> Result { + let mut s = serializer.serialize_struct("GolemUserPrincipal", 1)?; + s.serialize_field("accountId", &uuid_to_string(&self.account_id.uuid))?; + s.end() + } +} + +impl<'de> Deserialize<'de> for GolemUserPrincipal { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Helper { + account_id: String, + } + let h = Helper::deserialize(deserializer)?; + let uuid = uuid_from_string(&h.account_id).map_err(de::Error::custom)?; + Ok(GolemUserPrincipal { + account_id: AccountId { uuid }, + }) + } +} + +impl Serialize for Principal { + fn serialize(&self, serializer: S) -> Result { + match self { + Principal::Anonymous => { + let mut s = serializer.serialize_struct("Principal", 1)?; + s.serialize_field("tag", "anonymous")?; + s.end() + } + Principal::Oidc(oidc) => { + let mut s = serializer.serialize_struct("Principal", 2)?; + s.serialize_field("tag", "oidc")?; + s.serialize_field("val", oidc)?; + s.end() + } + Principal::Agent(agent) => { + let mut s = serializer.serialize_struct("Principal", 2)?; + s.serialize_field("tag", "agent")?; + s.serialize_field("val", agent)?; + s.end() + } + Principal::GolemUser(user) => { + let mut s = serializer.serialize_struct("Principal", 2)?; + s.serialize_field("tag", "golem-user")?; + s.serialize_field("val", user)?; + s.end() + } + } + } +} + +impl<'de> Deserialize<'de> for Principal { + fn deserialize>(deserializer: D) -> Result { + struct PrincipalVisitor; + + impl<'de> Visitor<'de> for PrincipalVisitor { + type Value = Principal; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a Principal object with a 'tag' field") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut tag_field: Option = None; + let mut val_field: Option = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "tag" => tag_field = Some(map.next_value()?), + "val" => val_field = Some(map.next_value()?), + _ => { + let _ = map.next_value::()?; + } + } + } + + let tag_str = tag_field.ok_or_else(|| de::Error::missing_field("tag"))?; + + match tag_str.as_str() { + "anonymous" => Ok(Principal::Anonymous), + "oidc" => { + let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; + let oidc: OidcPrincipal = + serde_json::from_value(val).map_err(de::Error::custom)?; + Ok(Principal::Oidc(oidc)) + } + "agent" => { + let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; + let agent: AgentPrincipal = + serde_json::from_value(val).map_err(de::Error::custom)?; + Ok(Principal::Agent(agent)) + } + "golem-user" => { + let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; + let user: GolemUserPrincipal = + serde_json::from_value(val).map_err(de::Error::custom)?; + Ok(Principal::GolemUser(user)) + } + other => Err(de::Error::unknown_variant( + other, + &["anonymous", "oidc", "agent", "golem-user"], + )), + } + } + } + + deserializer.deserialize_map(PrincipalVisitor) + } +} + +impl IntoValue for OidcPrincipal { + fn add_to_builder(self, builder: T) -> T::Result { + let builder = builder.record(); + let builder = self.sub.add_to_builder(builder.item()); + let builder = self.issuer.add_to_builder(builder.item()); + let builder = self.email.add_to_builder(builder.item()); + let builder = self.name.add_to_builder(builder.item()); + let builder = self.email_verified.add_to_builder(builder.item()); + let builder = self.given_name.add_to_builder(builder.item()); + let builder = self.family_name.add_to_builder(builder.item()); + let builder = self.picture.add_to_builder(builder.item()); + let builder = self.preferred_username.add_to_builder(builder.item()); + let builder = self.claims.add_to_builder(builder.item()); + builder.finish() + } + + fn add_to_type_builder(builder: T) -> T::Result { + let builder = builder.record( + Some("OidcPrincipal".to_string()), + Some("golem:agent".to_string()), + ); + let builder = ::add_to_type_builder(builder.field("sub")); + let builder = ::add_to_type_builder(builder.field("issuer")); + let builder = >::add_to_type_builder(builder.field("email")); + let builder = >::add_to_type_builder(builder.field("name")); + let builder = >::add_to_type_builder(builder.field("email_verified")); + let builder = >::add_to_type_builder(builder.field("given_name")); + let builder = >::add_to_type_builder(builder.field("family_name")); + let builder = >::add_to_type_builder(builder.field("picture")); + let builder = >::add_to_type_builder(builder.field("preferred_username")); + let builder = ::add_to_type_builder(builder.field("claims")); + builder.finish() + } +} + +impl FromValueAndType for OidcPrincipal { + fn from_extractor<'a, 'b>( + extractor: &'a impl WitValueExtractor<'a, 'b>, + ) -> Result { + let sub = ::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing sub".to_string())?, + )?; + let issuer = ::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing issuer".to_string())?, + )?; + let email = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing email".to_string())?, + )?; + let name = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing name".to_string())?, + )?; + let email_verified = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing email_verified".to_string())?, + )?; + let given_name = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing given_name".to_string())?, + )?; + let family_name = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing family_name".to_string())?, + )?; + let picture = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing picture".to_string())?, + )?; + let preferred_username = >::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing preferred_username".to_string())?, + )?; + let claims = ::from_extractor( + &extractor + .field(0) + .ok_or_else(|| "Missing claims".to_string())?, + )?; + + Ok(Self { + sub, + issuer, + email, + name, + email_verified, + given_name, + family_name, + picture, + preferred_username, + claims, + }) + } +} + +impl IntoValue for AgentPrincipal { + fn add_to_builder(self, builder: T) -> T::Result { + let builder = builder.record(); + let builder = self.agent_id.add_to_builder(builder.item()); + builder.finish() + } + + fn add_to_type_builder(builder: T) -> T::Result { + let builder = builder.record( + Some("AgentPrincipal".to_string()), + Some("golem:agent".to_string()), + ); + let builder = ::add_to_type_builder(builder.field("agent_id")); + builder.finish() + } +} + +impl FromValueAndType for AgentPrincipal { + fn from_extractor<'a, 'b>( + extractor: &'a impl WitValueExtractor<'a, 'b>, + ) -> Result { + let agent_id = ::from_extractor( + &extractor + .field(0usize) + .ok_or_else(|| "Missing agent_id field".to_string())?, + )?; + + Ok(Self { agent_id }) + } +} + +impl IntoValue for GolemUserPrincipal { + fn add_to_builder(self, builder: T) -> T::Result { + let builder = builder.record(); + let builder = self.account_id.add_to_builder(builder.item()); + builder.finish() + } + + fn add_to_type_builder(builder: T) -> T::Result { + let builder = builder.record( + Some("GolemUserPrincipal".to_string()), + Some("golem:agent".to_string()), + ); + let builder = ::add_to_type_builder(builder.field("account_id")); + builder.finish() + } +} + +impl FromValueAndType for GolemUserPrincipal { + fn from_extractor<'a, 'b>( + extractor: &'a impl WitValueExtractor<'a, 'b>, + ) -> Result { + let account_id = ::from_extractor( + &extractor + .field(0usize) + .ok_or_else(|| "Missing account_id field".to_string())?, + )?; + + Ok(Self { account_id }) + } +} + +impl IntoValue for Principal { + fn add_to_builder(self, builder: T) -> T::Result { + match self { + Principal::Oidc(inner) => { + let builder = builder.variant(0u32); + inner.add_to_builder(builder).finish() + } + Principal::Agent(inner) => { + let builder = builder.variant(1u32); + inner.add_to_builder(builder).finish() + } + Principal::GolemUser(inner) => { + let builder = builder.variant(2u32); + inner.add_to_builder(builder).finish() + } + Principal::Anonymous => builder.variant_unit(3u32), + } + } + + fn add_to_type_builder(builder: T) -> T::Result { + let builder = builder.variant( + Some("Principal".to_string()), + Some("golem:agent".to_string()), + ); + let builder = ::add_to_type_builder(builder.case("oidc")); + let builder = ::add_to_type_builder(builder.case("agent")); + let builder = ::add_to_type_builder(builder.case("golem-user")); + let builder = builder.unit_case("anonymous"); + builder.finish() + } +} + +impl FromValueAndType for Principal { + fn from_extractor<'a, 'b>( + extractor: &'a impl WitValueExtractor<'a, 'b>, + ) -> Result { + let (idx, inner) = extractor + .variant() + .ok_or_else(|| "Expected Principal to be a variant".to_string())?; + match idx { + 0 => { + let value = ::from_extractor( + &inner.ok_or_else(|| "Missing Principal::Oidc body".to_string())?, + )?; + Ok(Principal::Oidc(value)) + } + 1 => { + let value = ::from_extractor( + &inner.ok_or_else(|| "Missing Principal::Agent body".to_string())?, + )?; + Ok(Principal::Agent(value)) + } + 2 => { + let value = ::from_extractor( + &inner.ok_or_else(|| "Missing Principal::GolemUser body".to_string())?, + )?; + Ok(Principal::GolemUser(value)) + } + 3 => Ok(Principal::Anonymous), + _ => Err(format!("Invalid Principal variant index: {}", idx)), + } + } +} diff --git a/sdks/rust/golem-rust/src/agentic/principal_serde.rs b/sdks/rust/golem-rust/src/agentic/principal_serde.rs deleted file mode 100644 index 84f7891b75..0000000000 --- a/sdks/rust/golem-rust/src/agentic/principal_serde.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2024-2026 Golem Cloud -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::golem_agentic::golem::agent::common::{ - AgentPrincipal, GolemUserPrincipal, OidcPrincipal, Principal, -}; -use golem_wasm::golem_core_1_5_x::types::{AccountId, AgentId, ComponentId, Uuid}; -use serde::de::{self, MapAccess, Visitor}; -use serde::ser::SerializeStruct; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -fn uuid_to_string(uuid: &Uuid) -> String { - uuid::Uuid::from_u64_pair(uuid.high_bits, uuid.low_bits).to_string() -} - -fn uuid_from_string(s: &str) -> Result { - let parsed = uuid::Uuid::parse_str(s)?; - let (high_bits, low_bits) = parsed.as_u64_pair(); - Ok(Uuid { - high_bits, - low_bits, - }) -} - -impl Serialize for OidcPrincipal { - fn serialize(&self, serializer: S) -> Result { - let mut s = serializer.serialize_struct("OidcPrincipal", 10)?; - s.serialize_field("sub", &self.sub)?; - s.serialize_field("issuer", &self.issuer)?; - s.serialize_field("email", &self.email)?; - s.serialize_field("name", &self.name)?; - s.serialize_field("emailVerified", &self.email_verified)?; - s.serialize_field("givenName", &self.given_name)?; - s.serialize_field("familyName", &self.family_name)?; - s.serialize_field("picture", &self.picture)?; - s.serialize_field("preferredUsername", &self.preferred_username)?; - s.serialize_field("claims", &self.claims)?; - s.end() - } -} - -impl<'de> Deserialize<'de> for OidcPrincipal { - fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct Helper { - sub: String, - issuer: String, - email: Option, - name: Option, - email_verified: Option, - given_name: Option, - family_name: Option, - picture: Option, - preferred_username: Option, - claims: String, - } - let h = Helper::deserialize(deserializer)?; - Ok(OidcPrincipal { - sub: h.sub, - issuer: h.issuer, - email: h.email, - name: h.name, - email_verified: h.email_verified, - given_name: h.given_name, - family_name: h.family_name, - picture: h.picture, - preferred_username: h.preferred_username, - claims: h.claims, - }) - } -} - -impl Serialize for AgentPrincipal { - fn serialize(&self, serializer: S) -> Result { - let mut s = serializer.serialize_struct("AgentPrincipal", 2)?; - s.serialize_field( - "componentId", - &uuid_to_string(&self.agent_id.component_id.uuid), - )?; - s.serialize_field("agentId", &self.agent_id.agent_id)?; - s.end() - } -} - -impl<'de> Deserialize<'de> for AgentPrincipal { - fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct Helper { - component_id: String, - agent_id: String, - } - let h = Helper::deserialize(deserializer)?; - let uuid = uuid_from_string(&h.component_id).map_err(de::Error::custom)?; - Ok(AgentPrincipal { - agent_id: AgentId { - component_id: ComponentId { uuid }, - agent_id: h.agent_id, - }, - }) - } -} - -impl Serialize for GolemUserPrincipal { - fn serialize(&self, serializer: S) -> Result { - let mut s = serializer.serialize_struct("GolemUserPrincipal", 1)?; - s.serialize_field("accountId", &uuid_to_string(&self.account_id.uuid))?; - s.end() - } -} - -impl<'de> Deserialize<'de> for GolemUserPrincipal { - fn deserialize>(deserializer: D) -> Result { - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct Helper { - account_id: String, - } - let h = Helper::deserialize(deserializer)?; - let uuid = uuid_from_string(&h.account_id).map_err(de::Error::custom)?; - Ok(GolemUserPrincipal { - account_id: AccountId { uuid }, - }) - } -} - -impl Serialize for Principal { - fn serialize(&self, serializer: S) -> Result { - match self { - Principal::Anonymous => { - let mut s = serializer.serialize_struct("Principal", 1)?; - s.serialize_field("tag", "anonymous")?; - s.end() - } - Principal::Oidc(oidc) => { - let mut s = serializer.serialize_struct("Principal", 2)?; - s.serialize_field("tag", "oidc")?; - s.serialize_field("val", oidc)?; - s.end() - } - Principal::Agent(agent) => { - let mut s = serializer.serialize_struct("Principal", 2)?; - s.serialize_field("tag", "agent")?; - s.serialize_field("val", agent)?; - s.end() - } - Principal::GolemUser(user) => { - let mut s = serializer.serialize_struct("Principal", 2)?; - s.serialize_field("tag", "golem-user")?; - s.serialize_field("val", user)?; - s.end() - } - } - } -} - -impl<'de> Deserialize<'de> for Principal { - fn deserialize>(deserializer: D) -> Result { - struct PrincipalVisitor; - - impl<'de> Visitor<'de> for PrincipalVisitor { - type Value = Principal; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a Principal object with a 'tag' field") - } - - fn visit_map>(self, mut map: A) -> Result { - let mut tag_field: Option = None; - let mut val_field: Option = None; - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "tag" => tag_field = Some(map.next_value()?), - "val" => val_field = Some(map.next_value()?), - _ => { - let _ = map.next_value::()?; - } - } - } - - let tag_str = tag_field.ok_or_else(|| de::Error::missing_field("tag"))?; - - match tag_str.as_str() { - "anonymous" => Ok(Principal::Anonymous), - "oidc" => { - let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; - let oidc: OidcPrincipal = - serde_json::from_value(val).map_err(de::Error::custom)?; - Ok(Principal::Oidc(oidc)) - } - "agent" => { - let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; - let agent: AgentPrincipal = - serde_json::from_value(val).map_err(de::Error::custom)?; - Ok(Principal::Agent(agent)) - } - "golem-user" => { - let val = val_field.ok_or_else(|| de::Error::missing_field("val"))?; - let user: GolemUserPrincipal = - serde_json::from_value(val).map_err(de::Error::custom)?; - Ok(Principal::GolemUser(user)) - } - other => Err(de::Error::unknown_variant( - other, - &["anonymous", "oidc", "agent", "golem-user"], - )), - } - } - } - - deserializer.deserialize_map(PrincipalVisitor) - } -} diff --git a/sdks/rust/golem-rust/src/agentic/schema.rs b/sdks/rust/golem-rust/src/agentic/schema.rs index 5043f047f3..ff84296857 100644 --- a/sdks/rust/golem-rust/src/agentic/schema.rs +++ b/sdks/rust/golem-rust/src/agentic/schema.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::golem_agentic::golem::agent::common::{ - DataValue, ElementSchema, ElementValue, Principal, -}; +use crate::golem_agentic::golem::agent::common::{DataValue, ElementSchema, ElementValue}; use crate::value_and_type::FromValueAndType; use crate::value_and_type::IntoValue; use golem_wasm::golem_core_1_5_x::types::ValueAndType; @@ -62,22 +60,15 @@ pub trait Schema { #[derive(Debug)] pub enum StructuredSchema { - AutoInject(AutoInjectedParamType), Default(ElementSchema), Multimodal(Vec<(String, ElementSchema)>), } -#[derive(Debug, Clone)] -pub enum AutoInjectedParamType { - Principal, -} - impl StructuredSchema { pub fn get_element_schema(self) -> Option { match self { StructuredSchema::Default(element_schema) => Some(element_schema), StructuredSchema::Multimodal(_) => None, - StructuredSchema::AutoInject(_) => None, } } } @@ -86,12 +77,6 @@ impl StructuredSchema { pub enum StructuredValue { Default(ElementValue), Multimodal(Vec<(String, ElementValue)>), - AutoInjected(AutoInjectedValue), -} - -#[derive(Debug)] -pub enum AutoInjectedValue { - Principal(Principal), } impl StructuredValue { @@ -99,7 +84,6 @@ impl StructuredValue { match self { StructuredValue::Default(element_value) => Some(element_value), StructuredValue::Multimodal(_) => None, - StructuredValue::AutoInjected(_) => None, } } @@ -107,54 +91,8 @@ impl StructuredValue { match self { StructuredValue::Default(_) => None, StructuredValue::Multimodal(multimodal_values) => Some(multimodal_values), - StructuredValue::AutoInjected(_) => None, } } - - pub fn get_principal_value(self) -> Option { - match self { - StructuredValue::AutoInjected(AutoInjectedValue::Principal(principal)) => { - Some(principal) - } - _ => None, - } - } -} - -impl Schema for Principal { - fn get_type() -> StructuredSchema { - StructuredSchema::AutoInject(AutoInjectedParamType::Principal) - } - - fn to_structured_value(self) -> Result { - Ok(StructuredValue::AutoInjected(AutoInjectedValue::Principal( - self, - ))) - } - - fn from_structured_value( - value: StructuredValue, - schema: StructuredSchema, - ) -> Result - where - Self: Sized, - { - match (value, schema) { - ( - StructuredValue::AutoInjected(AutoInjectedValue::Principal(principal)), - StructuredSchema::AutoInject(AutoInjectedParamType::Principal), - ) => Ok(principal), - _ => Err("Mismatched value and schema for Principal".to_string()), - } - } - - fn to_element_value(self) -> Result { - Err("Principal is not expected to be converted to ElementValue".to_string()) - } - - fn from_element_value(_value: ElementValue) -> Result { - Err("Principal is not expected to be converted from ElementValue".to_string()) - } } // Handles the component model types via the IntoValue and FromValueAndType traits diff --git a/sdks/rust/golem-rust/src/agentic/unstructured_binary.rs b/sdks/rust/golem-rust/src/agentic/unstructured_binary.rs index 9464b6ba2c..1f9b76df41 100644 --- a/sdks/rust/golem-rust/src/agentic/unstructured_binary.rs +++ b/sdks/rust/golem-rust/src/agentic/unstructured_binary.rs @@ -68,9 +68,6 @@ impl Schema for UnstructuredBinary { StructuredValue::Multimodal(_) => { Err("type mismatch. expected default value, found multimodal") } - StructuredValue::AutoInjected(_) => { - Err("type mismatch. expected default value, found auto-injected") - } }?; match element_value { diff --git a/sdks/rust/golem-rust/src/agentic/unstructured_text.rs b/sdks/rust/golem-rust/src/agentic/unstructured_text.rs index 5fa16dc7dc..49cec6fb0d 100644 --- a/sdks/rust/golem-rust/src/agentic/unstructured_text.rs +++ b/sdks/rust/golem-rust/src/agentic/unstructured_text.rs @@ -73,9 +73,6 @@ impl Schema for UnstructuredText { StructuredValue::Multimodal(_) => { Err("input mismatch. expected default value, found multimodal".to_string()) } - StructuredValue::AutoInjected(_) => { - Err("input mismatch. expected default value, found auto-injected".to_string()) - } }?; match element_value { diff --git a/sdks/rust/golem-rust/tests/agent.rs b/sdks/rust/golem-rust/tests/agent.rs index aa4a3ebd35..15226e5e5f 100644 --- a/sdks/rust/golem-rust/tests/agent.rs +++ b/sdks/rust/golem-rust/tests/agent.rs @@ -799,20 +799,20 @@ mod tests { // Principal last parameter #[agent_definition] pub trait AgentWithPrincipalAutoInjection1 { - fn new(name: String, principal: Principal) -> Self; + fn new(name: String, #[principal] principal: Principal) -> Self; - fn foo(&self, name: String, principal: Principal) -> String; + fn foo(&self, name: String, #[principal] principal: Principal) -> String; } pub struct AgentWithPrincipalAutoInjection1Impl; #[agent_implementation] impl AgentWithPrincipalAutoInjection1 for AgentWithPrincipalAutoInjection1Impl { - fn new(_name: String, _principal: Principal) -> Self { + fn new(_name: String, #[principal] _principal: Principal) -> Self { Self } - fn foo(&self, name: String, _principal: Principal) -> String { + fn foo(&self, name: String, #[principal] _principal: Principal) -> String { name } } @@ -820,33 +820,55 @@ mod tests { // Auto Injected Principal Compilation Tests #[agent_definition] pub trait AgentWithPrincipalAutoInjection2 { - fn new(name: String, text1: u64, principal: Principal, text: String) -> Self; + fn new(name: String, text1: u64, #[principal] principal: Principal, text: String) -> Self; - fn foo(&self, name: String, num: u64, principal: Principal, text: String) -> String; + fn foo( + &self, + name: String, + num: u64, + #[principal] principal: Principal, + text: String, + ) -> String; } pub struct AgentWithPrincipalAutoInjection2Impl; #[agent_implementation] impl AgentWithPrincipalAutoInjection2 for AgentWithPrincipalAutoInjection2Impl { - fn new(_name: String, _text1: u64, _principal: Principal, _text: String) -> Self { + fn new( + _name: String, + _text1: u64, + #[principal] _principal: Principal, + _text: String, + ) -> Self { Self } - fn foo(&self, name: String, _num: u64, _principal: Principal, _text: String) -> String { + fn foo( + &self, + name: String, + _num: u64, + #[principal] _principal: Principal, + _text: String, + ) -> String { name } } #[agent_definition] pub trait AgentWithPrincipalAutoInjection3 { - fn new(name: String, text1: u64, principal: Principal, text: Option) -> Self; + fn new( + name: String, + text1: u64, + #[principal] principal: Principal, + text: Option, + ) -> Self; fn foo( &self, name: String, text1: u64, - principal: Principal, + #[principal] principal: Principal, text: Option, ) -> String; @@ -867,7 +889,12 @@ mod tests { #[agent_implementation] impl AgentWithPrincipalAutoInjection3 for AgentWithPrincipalAutoInjection5Impl { - fn new(_name: String, _text1: u64, _principal: Principal, _text: Option) -> Self { + fn new( + _name: String, + _text1: u64, + #[principal] _principal: Principal, + _text: Option, + ) -> Self { Self } @@ -875,7 +902,7 @@ mod tests { &self, name: String, _text1: u64, - _principal: Principal, + #[principal] _principal: Principal, _text: Option, ) -> String { name @@ -920,7 +947,7 @@ mod tests { #[agent_definition] pub trait RemoteAgentWithPrincipal { - fn new(name: String, principal: Principal) -> Self; + fn new(name: String, #[principal] principal: Principal) -> Self; async fn foo(&self, name: String) -> String; } @@ -928,7 +955,7 @@ mod tests { #[agent_implementation] impl RemoteAgentWithPrincipal for RemoteAgentWithPrincipalImpl { - fn new(_name: String, _principal: Principal) -> Self { + fn new(_name: String, #[principal] _principal: Principal) -> Self { Self } diff --git a/test-components/agent-sdk-rust/src/http/agent.rs b/test-components/agent-sdk-rust/src/http/agent.rs index 9290ce3b82..9db99a4013 100644 --- a/test-components/agent-sdk-rust/src/http/agent.rs +++ b/test-components/agent-sdk-rust/src/http/agent.rs @@ -1,9 +1,10 @@ use super::model::*; use golem_rust::{agent_definition, agent_implementation, endpoint, AllowedMimeTypes}; -use golem_rust::agentic::{create_webhook, UnstructuredBinary}; +use golem_rust::agentic::{create_webhook, UnstructuredBinary, Principal}; use serde::Deserialize; use serde::Serialize; use wstd::http::{Body, Client, HeaderValue, Request}; +use golem_rust::Schema; #[agent_definition(mount = "/http-agents/{agent_name}")] pub trait HttpAgent { @@ -340,3 +341,83 @@ impl WebhookAgent for WebhookAgentImpl { } } } + +#[agent_definition( + mount = "/principal-agent/{agent_name}", +)] +pub trait PrincipalAgent { + fn new(agent_name: String) -> Self; + + #[endpoint(get = "/echo-principal")] + fn echo_principal(&self, #[principal] principal: Principal) -> EchoPrincipalResponse; + + #[endpoint(get = "/echo-principal-mid/{foo}/{bar}")] + fn echo_principal2(&self, foo: String, #[principal] principal: Principal, bar: u32) -> EchoPrincipal2Response; + + #[endpoint(get = "/echo-principal-last/{foo}/{bar}")] + fn echo_principal3(&self, foo: String, bar: u32, #[principal] principal: Principal) -> EchoPrincipal3Response; + + #[endpoint(get = "/authed-principal", auth = true)] + fn authed_principal(&self, #[principal] principal: Principal) -> AuthedPrincipalResponse; +} + +#[derive(Schema)] +pub struct EchoPrincipalResponse { + pub value: Principal +} + +#[derive(Schema)] +pub struct EchoPrincipal2Response { + pub value: Principal, + pub foo: String, + pub bar: u32 +} + +#[derive(Schema)] +pub struct EchoPrincipal3Response { + pub value: Principal, + pub foo: String, + pub bar: u32 +} + +#[derive(Schema)] +pub struct AuthedPrincipalResponse { + pub value: Principal, +} + +pub struct PrincipalAgentImpl; + +#[agent_implementation] +impl PrincipalAgent for PrincipalAgentImpl { + fn new(_agent_name: String) -> Self { + Self + } + + fn echo_principal(&self, #[principal] principal: Principal) -> EchoPrincipalResponse { + EchoPrincipalResponse { + value: principal + } + } + + fn echo_principal2(&self, foo: String, #[principal] principal: Principal, bar: u32) -> EchoPrincipal2Response { + EchoPrincipal2Response { + value: principal, + foo, + bar + } + } + + fn echo_principal3(&self, foo: String, bar: u32, #[principal] principal: Principal) -> EchoPrincipal3Response { + EchoPrincipal3Response { + value: principal, + foo, + bar + } + } + + fn authed_principal(&self, #[principal] principal: Principal) -> AuthedPrincipalResponse { + AuthedPrincipalResponse { + value: principal + } + } +} diff --git a/test-components/agent-sdk-rust/src/http/model.rs b/test-components/agent-sdk-rust/src/http/model.rs index b03332faf0..bec97617ed 100644 --- a/test-components/agent-sdk-rust/src/http/model.rs +++ b/test-components/agent-sdk-rust/src/http/model.rs @@ -70,5 +70,4 @@ pub struct PreflightRequest { #[derive(Schema)] pub struct WebhookResponse { pub payload_length: u64, - }