test_r::enable!();
#[cfg(test)]
#[cfg(feature = "export_golem_agentic")]
#[test_r::sequential]
mod tests {
use golem_rust::agentic::{
AgentTypeName, Multimodal, MultimodalAdvanced, MultimodalCustom, Schema,
UnstructuredBinary, UnstructuredText,
};
use golem_rust::agentic::{Principal, create_webhook};
use golem_rust::golem_agentic::golem::agent::common::{
AgentConfigDeclaration, AgentConfigSource, AgentMode, AgentType, ElementValue,
Snapshotting, SnapshottingConfig,
};
use golem_rust::value_and_type::IntoValue;
use golem_rust::{AllowedLanguages, AllowedMimeTypes, ConfigSchema, MultimodalSchema};
use golem_rust::{Schema, agent_definition, agent_implementation, agentic::BaseAgent};
use golem_rust_macro::{FromValueAndType, IntoValue, description, endpoint, prompt};
use std::fmt::Debug;
use test_r::test;
use wasip2::clocks::wall_clock::Datetime;
#[derive(Clone, Debug, Schema)]
struct Config {
model: String,
}
#[agent_definition]
trait SampleAgent: BaseAgent {
fn new(init: String) -> Self;
fn num(&self, i: String) -> u32;
}
struct SampleAgentImpl {
_id: String,
}
#[agent_implementation]
impl SampleAgent for SampleAgentImpl {
fn new(init: String) -> Self {
SampleAgentImpl { _id: init }
}
fn num(&self, _i: String) -> u32 {
42
}
}
struct AgentWithTypeParameterImpl<T> {
_request_id: T,
}
#[agent_definition]
trait AgentWithTypeParameter<T: Schema + Clone + Debug> {
fn new(init: String) -> Self;
fn num(&self, i: String) -> u32;
}
#[agent_implementation]
impl AgentWithTypeParameter<String> for AgentWithTypeParameterImpl<String> {
fn new(init: String) -> Self {
AgentWithTypeParameterImpl { _request_id: init }
}
fn num(&self, _i: String) -> u32 {
1
}
}
struct AgentWithStaticMethodsImpl;
#[agent_definition]
trait AgentWithStaticMethods {
fn new(init: UserId) -> Self;
#[allow(unused)]
fn foo(&self, param: String) -> String;
#[allow(unused)]
fn bar(&self, param: String) -> String;
fn baz(&self, param: String) -> String;
}
#[agent_implementation]
impl AgentWithStaticMethods for AgentWithStaticMethodsImpl {
fn new(_init: UserId) -> Self {
AgentWithStaticMethodsImpl
}
fn foo(&self, param: String) -> String {
param
}
fn bar(&self, param: String) -> String {
self.foo(param)
}
fn baz(&self, param: String) -> String {
param
}
}
#[agent_definition]
trait AgentWithOnlyConstructor {
fn new(init: UserId) -> Self;
}
struct AgentWithOnlyConstructorImpl;
#[agent_implementation]
impl AgentWithOnlyConstructor for AgentWithOnlyConstructorImpl {
fn new(_init: UserId) -> Self {
AgentWithOnlyConstructorImpl
}
}
#[agent_definition]
trait AgentWithOnlyStaticMethods {
fn new(init: UserId) -> Self;
#[allow(unused)]
fn foo(&self) -> String;
#[allow(unused)]
fn bar(&self, param: String) -> String;
}
struct AgentWithOnlyStaticMethodsImpl;
#[agent_implementation]
impl AgentWithOnlyStaticMethods for AgentWithOnlyStaticMethodsImpl {
fn new(_init: UserId) -> Self {
AgentWithOnlyStaticMethodsImpl
}
fn foo(&self) -> String {
self.bar("foo".to_string())
}
fn bar(&self, param: String) -> String {
param
}
}
#[agent_definition]
trait FooAgent {
fn new(init: UserId) -> Self;
#[allow(unused)]
fn foo(&self) -> String;
fn bar(&self) -> String;
}
struct FooImpl;
#[agent_implementation]
impl FooAgent for FooImpl {
fn new(_init: UserId) -> FooImpl {
FooImpl
}
fn foo(&self) -> String {
"foo".to_string()
}
fn bar(&self) -> String {
"bar".to_string()
}
}
#[agent_definition]
trait Echo {
fn new(init: UserId, llm_config: Config) -> Self;
fn echo_mut(&mut self, message: String) -> String;
fn echo(&self, message: String) -> String;
fn get_id(&self) -> String;
fn echo_result(&self, result: Result<(), ()>) -> Result<(), ()>;
fn echo_result_err(&self, result: Result<(), String>) -> Result<(), String>;
fn echo_result_ok(&self, result: Result<String, ()>) -> Result<String, ()>;
fn echo_option(&self, option: Option<String>) -> Option<String>;
fn echo_multimodal_advanced(
&self,
input: MultimodalAdvanced<TextOrImage>,
) -> MultimodalAdvanced<TextOrImage>;
fn echo_multimodal(&self, input: Multimodal) -> Multimodal;
fn echo_multimodal_custom(
&self,
input: MultimodalCustom<TextOrImage>,
) -> MultimodalCustom<TextOrImage>;
fn echo_unstructured_text(&self, input: UnstructuredText) -> UnstructuredText;
fn echo_unstructured_text_lc(
&self,
input: UnstructuredText<MyLang>,
) -> UnstructuredText<MyLang>;
fn echo_unstructured_binary(
&self,
input: UnstructuredBinary<MyMimeType>,
) -> UnstructuredBinary<MyMimeType>;
}
struct EchoImpl {
_id: UserId,
_llm_config: Config,
}
#[derive(Debug, Clone, AllowedLanguages)]
enum MyLang {
#[code("de")]
German,
En,
}
#[derive(Debug, Clone, AllowedMimeTypes)]
enum MyMimeType {
#[mime_type("text/plain")]
PlainText,
#[mime_type("image/png")]
PngImage,
}
#[agent_implementation]
impl Echo for EchoImpl {
fn new(id: UserId, llm_config: Config) -> Self {
EchoImpl {
_id: id,
_llm_config: llm_config,
}
}
fn echo_mut(&mut self, message: String) -> String {
format!("Echo: {}", message)
}
fn echo(&self, message: String) -> String {
message.to_string()
}
fn get_id(&self) -> String {
self.get_agent_id()
}
fn echo_result(&self, result: Result<(), ()>) -> Result<(), ()> {
result
}
fn echo_result_err(&self, result: Result<(), String>) -> Result<(), String> {
result
}
fn echo_result_ok(&self, result: Result<String, ()>) -> Result<String, ()> {
result
}
fn echo_option(&self, option: Option<String>) -> Option<String> {
option
}
fn echo_multimodal_advanced(
&self,
input: MultimodalAdvanced<TextOrImage>,
) -> MultimodalAdvanced<TextOrImage> {
input
}
fn echo_multimodal(&self, input: Multimodal) -> Multimodal {
input
}
fn echo_multimodal_custom(
&self,
input: MultimodalCustom<TextOrImage>,
) -> MultimodalCustom<TextOrImage> {
input
}
fn echo_unstructured_text(&self, input: UnstructuredText) -> UnstructuredText {
input
}
fn echo_unstructured_text_lc(
&self,
input: UnstructuredText<MyLang>,
) -> UnstructuredText<MyLang> {
input
}
fn echo_unstructured_binary(
&self,
input: UnstructuredBinary<MyMimeType>,
) -> UnstructuredBinary<MyMimeType> {
input
}
async fn load_snapshot(&mut self, _bytes: Vec<u8>) -> Result<(), String> {
Ok(())
}
async fn save_snapshot(&self) -> Result<Vec<u8>, String> {
Ok(vec![])
}
}
#[agent_definition(mode = "ephemeral")]
trait EchoEphemeralExplicit {
fn new(init: UserId) -> Self;
fn echo(&self, message: String) -> String;
}
struct EchoEphemeralExplicitImpl {
_id: UserId,
}
#[agent_implementation]
impl EchoEphemeralExplicit for EchoEphemeralExplicitImpl {
fn new(id: UserId) -> Self {
EchoEphemeralExplicitImpl { _id: id }
}
fn echo(&self, message: String) -> String {
message.to_string()
}
}
#[agent_definition(ephemeral)]
trait EchoEphemeralShorthand {
fn new(init: UserId) -> Self;
fn echo(&self, message: String) -> String;
}
struct EchoEphemeralShorthandImpl {
_id: UserId,
}
#[agent_implementation]
impl EchoEphemeralShorthand for EchoEphemeralShorthandImpl {
fn new(id: UserId) -> Self {
EchoEphemeralShorthandImpl { _id: id }
}
fn echo(&self, message: String) -> String {
message.to_string()
}
}
#[agent_definition(mode = "durable")]
trait EchoDurableExplicit {
fn new(init: UserId) -> Self;
fn echo(&self, message: String) -> String;
}
struct EchoDurableExplicitImpl {
_id: UserId,
}
#[agent_implementation]
impl EchoDurableExplicit for EchoDurableExplicitImpl {
fn new(id: UserId) -> Self {
EchoDurableExplicitImpl { _id: id }
}
fn echo(&self, message: String) -> String {
message.to_string()
}
}
#[agent_definition]
trait EchoDurableDefault {
fn new(init: UserId) -> Self;
fn echo(&self, message: String) -> String;
}
struct EchoDurableDefaultImpl {
_id: UserId,
}
#[agent_implementation]
impl EchoDurableDefault for EchoDurableDefaultImpl {
fn new(id: UserId) -> Self {
EchoDurableDefaultImpl { _id: id }
}
fn echo(&self, message: String) -> String {
message.to_string()
}
}
#[agent_definition]
trait EchoAsync {
async fn new(init: UserId, llm_config: Config) -> Self;
async fn echo_mut(&mut self, message: String) -> String;
async fn echo(&self, message: String) -> String;
async fn get_id(&self) -> String;
async fn echo_result(&self, result: Result<(), ()>) -> Result<(), ()>;
async fn echo_result_err(&self, result: Result<(), String>) -> Result<(), String>;
async fn echo_result_ok(&self, result: Result<String, ()>) -> Result<String, ()>;
async fn echo_option(&self, option: Option<String>) -> Option<String>;
async fn echo_multimodal_custom(
&self,
input: MultimodalAdvanced<TextOrImage>,
) -> MultimodalAdvanced<TextOrImage>;
async fn echo_multimodal(&self, input: Multimodal) -> Multimodal;
async fn echo_unstructured_text(&self, input: UnstructuredText) -> UnstructuredText;
async fn echo_unstructured_text_lc(
&self,
input: UnstructuredText<MyLang>,
) -> UnstructuredText<MyLang>;
async fn rpc_call(&self, string: String) -> String;
fn rpc_call_trigger(&self, string: String);
fn rpc_call_schedule(&self, string: String);
}
struct EchoAsyncImpl {
id: UserId,
llm_config: Config,
}
#[agent_implementation]
impl EchoAsync for EchoAsyncImpl {
async fn new(id: UserId, llm_config: Config) -> Self {
EchoAsyncImpl { id, llm_config }
}
async fn echo_mut(&mut self, message: String) -> String {
format!("Echo: {}", message)
}
async fn echo(&self, message: String) -> String {
message.to_string()
}
async fn get_id(&self) -> String {
self.get_agent_id()
}
async fn echo_result(&self, result: Result<(), ()>) -> Result<(), ()> {
result
}
async fn echo_result_err(&self, result: Result<(), String>) -> Result<(), String> {
result
}
async fn echo_result_ok(&self, result: Result<String, ()>) -> Result<String, ()> {
result
}
async fn echo_option(&self, option: Option<String>) -> Option<String> {
option
}
async fn echo_multimodal_custom(
&self,
input: MultimodalAdvanced<TextOrImage>,
) -> MultimodalAdvanced<TextOrImage> {
input
}
async fn echo_multimodal(&self, input: Multimodal) -> Multimodal {
input
}
async fn echo_unstructured_text(&self, input: UnstructuredText) -> UnstructuredText {
input
}
async fn echo_unstructured_text_lc(
&self,
input: UnstructuredText<MyLang>,
) -> UnstructuredText<MyLang> {
input
}
async fn rpc_call(&self, string: String) -> String {
let client = EchoClient::get(self.id.clone(), self.llm_config.clone());
client.echo(string).await
}
fn rpc_call_trigger(&self, string: String) {
let client = EchoClient::get(self.id.clone(), self.llm_config.clone());
client.trigger_echo(string);
}
fn rpc_call_schedule(&self, string: String) {
let client = EchoClient::get(self.id.clone(), self.llm_config.clone());
client.schedule_echo(
string,
Datetime {
seconds: 1,
nanoseconds: 1,
},
);
}
}
#[agent_definition]
trait AgentWithReservedMethods {
fn new(init: String) -> Self;
fn get(&self) -> String;
async fn remote_get(&mut self) -> String;
}
struct AgentWithGetFunctionImpl {}
#[agent_implementation]
impl AgentWithReservedMethods for AgentWithGetFunctionImpl {
fn new(_init: String) -> Self {
AgentWithGetFunctionImpl {}
}
fn get(&self) -> String {
"foo".to_string()
}
async fn remote_get(&mut self) -> String {
let client = AgentWithReservedMethodsClient::get_("increment".to_string());
client.get().await
}
}
#[derive(Schema, MultimodalSchema)]
enum TextOrImage {
Text(String),
Image(Vec<u8>),
}
#[derive(Schema, Clone)]
struct UserId {
id: String,
}
#[allow(clippy::assertions_on_constants)]
#[test] fn test_agent_compilation() {
assert!(true);
}
#[agent_definition]
#[description("a descriptive agent")]
pub trait DescriptiveAgent {
#[description("new constructor")]
#[prompt("new prompt")]
fn new(name: String) -> Self;
#[description("increment description")]
#[prompt("increment prompt")]
fn increment(&mut self) -> u32;
}
struct DescriptiveAgentImpl {}
impl DescriptiveAgent for DescriptiveAgentImpl {
fn new(_name: String) -> Self {
DescriptiveAgentImpl {}
}
fn increment(&mut self) -> u32 {
1
}
}
#[allow(unused)]
#[derive(ConfigSchema)]
struct ConfigAgentConfigNested {
foo: String,
bar: i32,
#[config_schema(secret)]
nested_secret: golem_rust::agentic::Secret<bool>,
}
#[allow(unused)]
#[derive(ConfigSchema)]
struct ConfigAgentConfig {
url: String,
port: u32,
#[config_schema(nested)]
nested: ConfigAgentConfigNested,
#[config_schema(secret)]
api_key: golem_rust::agentic::Secret<String>,
}
#[agent_definition]
trait ConfigAgent: BaseAgent {
fn new(#[agent_config] config: golem_rust::agentic::Config<ConfigAgentConfig>) -> Self;
}
struct ConfigAgentImpl;
#[agent_implementation]
impl ConfigAgent for ConfigAgentImpl {
fn new(#[agent_config] _config: golem_rust::agentic::Config<ConfigAgentConfig>) -> Self {
Self
}
}
#[test]
fn test_agent_description_and_prompts() {
DescriptiveAgentImpl::__register_agent_type();
let agent_name = AgentTypeName("DescriptiveAgent".to_string());
let agent =
golem_rust::agentic::get_agent_type_by_name(&agent_name).expect("Agent type not found");
let increment_method = agent.methods.first().expect("increment method not found");
assert_eq!(agent.description, "a descriptive agent");
assert_eq!(agent.constructor.description, "new constructor");
assert_eq!(
agent.constructor.prompt_hint,
Some("new prompt".to_string())
);
assert_eq!(increment_method.description, "increment description");
assert_eq!(
increment_method.prompt_hint,
Some("increment prompt".to_string())
);
}
#[test]
fn test_agent_modes() {
use golem_rust::agentic::get_all_agent_types;
let agent_types = get_all_agent_types();
let find_agent = |name: &str| -> Option<AgentType> {
agent_types.iter().find(|a| a.type_name == name).cloned()
};
if let Some(agent) = find_agent("EchoDurableDefault") {
assert_eq!(
&agent.mode,
&AgentMode::Durable,
"EchoDurableDefault should be Durable"
);
}
if let Some(agent) = find_agent("EchoDurableExplicit") {
assert_eq!(
&agent.mode,
&AgentMode::Durable,
"EchoDurableExplicit should be Durable"
);
}
if let Some(agent) = find_agent("EchoEphemeralShorthand") {
assert_eq!(
&agent.mode,
&AgentMode::Ephemeral,
"EchoEphemeralShorthand should be Ephemeral"
);
}
if let Some(agent) = find_agent("EchoEphemeralExplicit") {
assert_eq!(
&agent.mode,
&AgentMode::Ephemeral,
"EchoEphemeralExplicit should be Ephemeral"
);
}
}
#[agent_definition(
mount = "/chats/{agent-type}/{foo}/{bar}",
webhook_suffix = "/{agent-type}/events/{foo}/{bar}",
auth = true,
phantom_agent = true,
cors = ["https://app.acme.com", "https://staging.acme.com"],
)]
trait ComplexHttpAgent {
fn new(foo: String, bar: String) -> Self;
#[endpoint(
get = "/path-and-header/{resource_id}",
headers("X-Request-ID" = "request_id")
)]
fn path_and_header(&self, resource_id: String, request_id: String) -> String;
#[endpoint(get = "/greet?l={location}&n={name}")]
fn greet1(&self, location: String, name: String) -> String;
#[endpoint(get = "/greet?l={location}&n={name}")]
#[endpoint(get = "/greet?lx={location}&nm={name}", cors = ["*"], auth = true, headers("X-Foo" = "location", "X-Bar" = "name"))]
fn greet2(&self, location: String, name: String) -> String;
#[endpoint(get = "/greet/{name}/{*file_path}")]
fn greet3(&self, name: String, file_path: String) -> String;
#[endpoint(get = "/")]
fn empty_path(&self);
}
struct AgentWithHttpMountImpl {}
#[agent_implementation]
impl ComplexHttpAgent for AgentWithHttpMountImpl {
fn new(_foo: String, _bar: String) -> Self {
AgentWithHttpMountImpl {}
}
fn path_and_header(&self, _resource_id: String, _request_id: String) -> String {
"foo".to_string()
}
fn greet1(&self, _location: String, _name: String) -> String {
"foo".to_string()
}
fn greet2(&self, _location: String, _name: String) -> String {
"bar".to_string()
}
fn greet3(&self, _name: String, _file_path: String) -> String {
"baz".to_string()
}
fn empty_path(&self) {}
}
#[test]
fn test_agent_with_http() {
use golem_rust::agentic::get_all_agent_types;
let agent_types = get_all_agent_types();
let agent = agent_types
.iter()
.find(|a| a.type_name == "ComplexHttpAgent")
.expect("ComplexHttpAgent not found");
assert!(
agent.http_mount.is_some(),
"HTTP mount details should be set"
);
assert_eq!(
agent.http_mount.as_ref().map(|hm| hm.phantom_agent),
Some(true),
"Agent phantom property should be set"
);
assert!(
!agent.methods.is_empty(),
"Agent should have methods defined"
);
assert!(
agent.methods.iter().all(|m| !m.http_endpoint.is_empty()),
"All methods should have HTTP endpoint details"
);
assert!(agent.methods.iter().all(|m| !m.http_endpoint.is_empty()),)
}
#[agent_definition(mount = "/chats/{agent-type}")]
trait SimpleHttpAgent {
fn new() -> Self;
#[endpoint(get = "/green/{name}")]
fn greet(&self, name: String) -> String;
}
struct SimpleHttpAgentImpl;
#[agent_implementation]
impl SimpleHttpAgent for SimpleHttpAgentImpl {
fn new() -> Self {
SimpleHttpAgentImpl
}
fn greet(&self, name: String) -> String {
format!("Hello, {}!", name)
}
}
#[agent_definition]
pub trait AgentWithPrincipalAutoInjection1 {
fn new(name: String, principal: Principal) -> Self;
fn foo(&self, name: String, principal: Principal) -> String;
}
pub struct AgentWithPrincipalAutoInjection1Impl;
#[agent_implementation]
impl AgentWithPrincipalAutoInjection1 for AgentWithPrincipalAutoInjection1Impl {
fn new(_name: String, _principal: Principal) -> Self {
Self
}
fn foo(&self, name: String, _principal: Principal) -> String {
name
}
}
#[agent_definition]
pub trait AgentWithPrincipalAutoInjection2 {
fn new(name: String, text1: u64, principal: Principal, text: String) -> Self;
fn foo(&self, name: String, num: u64, 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 {
Self
}
fn foo(&self, name: String, _num: u64, _principal: Principal, _text: String) -> String {
name
}
}
#[agent_definition]
pub trait AgentWithPrincipalAutoInjection3 {
fn new(name: String, text1: u64, principal: Principal, text: Option<String>) -> Self;
fn foo(
&self,
name: String,
text1: u64,
principal: Principal,
text: Option<String>,
) -> String;
fn foo_without_principal_1(&self, name: String, num: u64) -> String;
fn foo_without_principal_2(&self, name: String, num: u64, text: String) -> String;
fn foo_without_principal_3(&self, name: String, num: u64, text: Option<String>) -> String;
fn foo_without_principal_4(
&self,
name: String,
num: u64,
text1: Option<String>,
text2: Option<String>,
) -> String;
fn foo_without_principal_5(&self, name: String, num: u64, text: Option<String>) -> String;
}
pub struct AgentWithPrincipalAutoInjection5Impl;
#[agent_implementation]
impl AgentWithPrincipalAutoInjection3 for AgentWithPrincipalAutoInjection5Impl {
fn new(_name: String, _text1: u64, _principal: Principal, _text: Option<String>) -> Self {
Self
}
fn foo(
&self,
name: String,
_text1: u64,
_principal: Principal,
_text: Option<String>,
) -> String {
name
}
fn foo_without_principal_1(&self, name: String, _num: u64) -> String {
name
}
fn foo_without_principal_2(&self, name: String, _num: u64, _text: String) -> String {
name
}
fn foo_without_principal_3(
&self,
name: String,
_num: u64,
_text: Option<String>,
) -> String {
name
}
fn foo_without_principal_4(
&self,
name: String,
_num: u64,
_text1: Option<String>,
_text2: Option<String>,
) -> String {
name
}
fn foo_without_principal_5(
&self,
name: String,
_num: u64,
_text: Option<String>,
) -> String {
name
}
}
#[agent_definition]
pub trait RemoteAgentWithPrincipal {
fn new(name: String, principal: Principal) -> Self;
async fn foo(&self, name: String) -> String;
}
pub struct RemoteAgentWithPrincipalImpl;
#[agent_implementation]
impl RemoteAgentWithPrincipal for RemoteAgentWithPrincipalImpl {
fn new(_name: String, _principal: Principal) -> Self {
Self
}
async fn foo(&self, name: String) -> String {
AgentWithPrincipalAutoInjection1Client::get(name.clone())
.foo(name.clone())
.await;
AgentWithPrincipalAutoInjection2Client::get(name.clone(), 1, "required".into())
.foo(name.clone(), 1, "required".into())
.await;
AgentWithPrincipalAutoInjection3Client::get(
name.clone(),
1,
Some("not-undefined".into()),
)
.foo_without_principal_1("name".into(), 1)
.await;
"finished".into()
}
}
#[agent_definition]
pub trait WebhookAgent {
fn new(name: String) -> Self;
async fn create_webhook_and_trigger(&self) -> String;
}
fn webhook_placeholder(_url: &str) -> String {
"webhook triggered".to_string()
}
pub struct WebhookAgentImpl;
#[agent_implementation]
impl WebhookAgent for WebhookAgentImpl {
fn new(_name: String) -> Self {
Self
}
async fn create_webhook_and_trigger(&self) -> String {
let webhook = create_webhook();
webhook_placeholder(webhook.url());
let request = webhook.await;
request.json().unwrap()
}
}
#[agent_definition]
trait AgentSnapshottingDefault {
fn new(init: String) -> Self;
fn echo(&self, message: String) -> String;
}
struct AgentSnapshottingDefaultImpl {
_id: String,
}
#[agent_implementation]
impl AgentSnapshottingDefault for AgentSnapshottingDefaultImpl {
fn new(id: String) -> Self {
Self { _id: id }
}
fn echo(&self, message: String) -> String {
message
}
}
#[agent_definition(snapshotting = "disabled")]
trait AgentSnapshottingDisabled {
fn new(init: String) -> Self;
fn echo(&self, message: String) -> String;
}
struct AgentSnapshottingDisabledImpl {
_id: String,
}
#[agent_implementation]
impl AgentSnapshottingDisabled for AgentSnapshottingDisabledImpl {
fn new(id: String) -> Self {
Self { _id: id }
}
fn echo(&self, message: String) -> String {
message
}
}
#[agent_definition(snapshotting = "enabled")]
trait AgentSnapshottingEnabled {
fn new(init: String) -> Self;
fn echo(&self, message: String) -> String;
}
struct AgentSnapshottingEnabledImpl {
_id: String,
}
#[agent_implementation]
impl AgentSnapshottingEnabled for AgentSnapshottingEnabledImpl {
fn new(id: String) -> Self {
Self { _id: id }
}
fn echo(&self, message: String) -> String {
message
}
}
#[agent_definition(snapshotting = "periodic(5s)")]
trait AgentSnapshottingPeriodic {
fn new(init: String) -> Self;
fn echo(&self, message: String) -> String;
}
struct AgentSnapshottingPeriodicImpl {
_id: String,
}
#[agent_implementation]
impl AgentSnapshottingPeriodic for AgentSnapshottingPeriodicImpl {
fn new(id: String) -> Self {
Self { _id: id }
}
fn echo(&self, message: String) -> String {
message
}
}
#[agent_definition(snapshotting = "every(10)")]
trait AgentSnapshottingEveryN {
fn new(init: String) -> Self;
fn echo(&self, message: String) -> String;
}
struct AgentSnapshottingEveryNImpl {
_id: String,
}
#[agent_implementation]
impl AgentSnapshottingEveryN for AgentSnapshottingEveryNImpl {
fn new(id: String) -> Self {
Self { _id: id }
}
fn echo(&self, message: String) -> String {
message
}
}
#[test]
fn test_agent_snapshotting() {
use golem_rust::agentic::get_all_agent_types;
let agent_types = get_all_agent_types();
let find_agent = |name: &str| -> Option<AgentType> {
agent_types.iter().find(|a| a.type_name == name).cloned()
};
if let Some(agent) = find_agent("AgentSnapshottingDefault") {
assert!(
matches!(agent.snapshotting, Snapshotting::Disabled),
"Default should be Disabled"
);
}
if let Some(agent) = find_agent("AgentSnapshottingDisabled") {
assert!(
matches!(agent.snapshotting, Snapshotting::Disabled),
"Explicit disabled should be Disabled"
);
}
if let Some(agent) = find_agent("AgentSnapshottingEnabled") {
assert!(
matches!(
agent.snapshotting,
Snapshotting::Enabled(SnapshottingConfig::Default)
),
"Enabled should use Default config"
);
}
if let Some(agent) = find_agent("AgentSnapshottingPeriodic") {
assert!(
matches!(
agent.snapshotting,
Snapshotting::Enabled(SnapshottingConfig::Periodic(5000000000))
),
"Periodic should have correct duration"
);
}
if let Some(agent) = find_agent("AgentSnapshottingEveryN") {
assert!(
matches!(
agent.snapshotting,
Snapshotting::Enabled(SnapshottingConfig::EveryNInvocation(10))
),
"EveryNInvocation should have correct count"
);
}
}
#[agent_definition]
trait SerializableAgent {
fn new(name: String) -> Self;
fn get_name(&self) -> String;
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SerializableAgentImpl {
name: String,
}
#[agent_implementation]
impl SerializableAgent for SerializableAgentImpl {
fn new(name: String) -> Self {
Self { name }
}
fn get_name(&self) -> String {
self.name.clone()
}
}
#[agent_definition]
trait NonSerializableAgent {
fn new(value: u32) -> Self;
fn get_value(&self) -> u32;
}
struct NonSerializableAgentImpl {
value: u32,
}
#[agent_implementation]
impl NonSerializableAgent for NonSerializableAgentImpl {
fn new(value: u32) -> Self {
Self { value }
}
fn get_value(&self) -> u32 {
self.value
}
}
#[agent_definition]
trait CustomSnapshotAgent {
fn new(data: String) -> Self;
fn get_data(&self) -> String;
}
struct CustomSnapshotAgentImpl {
data: String,
}
#[agent_implementation]
impl CustomSnapshotAgent for CustomSnapshotAgentImpl {
fn new(data: String) -> Self {
Self { data }
}
fn get_data(&self) -> String {
self.data.clone()
}
async fn load_snapshot(&mut self, bytes: Vec<u8>) -> Result<(), String> {
self.data = String::from_utf8(bytes).map_err(|e| e.to_string())?;
Ok(())
}
async fn save_snapshot(&self) -> Result<Vec<u8>, String> {
Ok(self.data.as_bytes().to_vec())
}
}
#[test]
fn test_serializable_agent_auto_json_snapshot() {
use golem_rust::agentic::snapshot_auto::LoadHelper;
use golem_rust::agentic::snapshot_auto::SaveHelper;
let agent = SerializableAgentImpl {
name: "test-agent".to_string(),
};
let helper = SaveHelper(&agent);
let result = helper.snapshot_save();
assert!(result.is_ok(), "Save should succeed for serializable agent");
let snapshot = result.unwrap();
assert_eq!(snapshot.mime_type, "application/json");
let json_value: serde_json::Value = serde_json::from_slice(&snapshot.data).unwrap();
assert_eq!(json_value["name"], "test-agent");
let mut agent2 = SerializableAgentImpl {
name: "old".to_string(),
};
let mut load_helper = LoadHelper(&mut agent2);
let load_result = load_helper.snapshot_load(&snapshot.data);
assert!(
load_result.is_ok(),
"Load should succeed for serializable agent"
);
assert_eq!(agent2.name, "test-agent");
}
#[test]
fn test_non_serializable_agent_snapshot_returns_error() {
use golem_rust::agentic::snapshot_auto::{LoadHelper, SnapshotLoadFallback};
use golem_rust::agentic::snapshot_auto::{SaveHelper, SnapshotSaveFallback};
let agent = NonSerializableAgentImpl { value: 42 };
let helper = SaveHelper(&agent);
let result = helper.snapshot_save();
assert!(
result.is_err(),
"Save should fail for non-serializable agent"
);
assert!(result.unwrap_err().contains("not implemented"));
let mut agent2 = NonSerializableAgentImpl { value: 0 };
let mut load_helper = LoadHelper(&mut agent2);
let load_result = load_helper.snapshot_load(b"{}");
assert!(
load_result.is_err(),
"Load should fail for non-serializable agent"
);
assert!(load_result.unwrap_err().contains("not implemented"));
}
#[test]
fn test_custom_snapshot_agent_uses_custom_methods() {
use golem_rust::agentic::BaseAgent;
let mut agent = CustomSnapshotAgentImpl {
data: "hello-world".to_string(),
};
let save_result = wstd::runtime::block_on(agent.save_snapshot_base());
assert!(save_result.is_ok());
let snapshot = save_result.unwrap();
assert_eq!(snapshot.mime_type, "application/octet-stream");
assert_eq!(snapshot.data, b"hello-world");
let load_result = wstd::runtime::block_on(agent.load_snapshot_base(b"new-data".to_vec()));
assert!(load_result.is_ok());
assert_eq!(agent.data, "new-data");
}
#[test]
fn test_agent_config_entries() {
ConfigAgentImpl::__register_agent_type();
let agent_name = AgentTypeName("ConfigAgent".to_string());
let agent =
golem_rust::agentic::get_agent_type_by_name(&agent_name).expect("Agent type not found");
fn project_for_comparsion(
mut value: AgentConfigDeclaration,
) -> (AgentConfigSource, Vec<String>, String) {
(
value.source,
value.path,
format!("{:?}", value.value_type.nodes.swap_remove(0).type_),
)
}
assert_eq!(
agent
.config
.into_iter()
.map(project_for_comparsion)
.collect::<Vec<_>>(),
vec![
(
AgentConfigSource::Local,
vec!["url".to_string()],
"WitTypeNode::PrimStringType".to_string()
),
(
AgentConfigSource::Local,
vec!["port".to_string()],
"WitTypeNode::PrimU32Type".to_string()
),
(
AgentConfigSource::Local,
vec!["nested".to_string(), "foo".to_string(),],
"WitTypeNode::PrimStringType".to_string()
),
(
AgentConfigSource::Local,
vec!["nested".to_string(), "bar".to_string(),],
"WitTypeNode::PrimS32Type".to_string()
),
(
AgentConfigSource::Secret,
vec!["nested".to_string(), "nested_secret".to_string(),],
"WitTypeNode::PrimBoolType".to_string()
),
(
AgentConfigSource::Secret,
vec!["api_key".to_string()],
"WitTypeNode::PrimStringType".to_string()
)
]
);
}
#[test]
fn test_from_element_value_roundtrip_string() {
let original = "hello world".to_string();
let ev = ElementValue::ComponentModel(original.clone().into_value());
let recovered = String::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
#[test]
fn test_from_element_value_roundtrip_u64() {
let original: u64 = 123456789;
let ev = ElementValue::ComponentModel(original.into_value());
let recovered = u64::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
#[test]
fn test_from_element_value_roundtrip_bool() {
for original in [true, false] {
let ev = ElementValue::ComponentModel(original.into_value());
let recovered = bool::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
}
#[test]
fn test_from_element_value_roundtrip_option() {
let some_val: Option<u32> = Some(42);
let ev = ElementValue::ComponentModel(some_val.into_value());
let recovered = Option::<u32>::from_element_value(ev).unwrap();
assert_eq!(recovered, Some(42));
let none_val: Option<u32> = None;
let ev = ElementValue::ComponentModel(none_val.into_value());
let recovered = Option::<u32>::from_element_value(ev).unwrap();
assert_eq!(recovered, None);
}
#[test]
fn test_from_element_value_roundtrip_vec() {
let original = vec![1u32, 2, 3, 4, 5];
let ev = ElementValue::ComponentModel(original.clone().into_value());
let recovered = Vec::<u32>::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
#[derive(IntoValue, FromValueAndType, PartialEq, Debug, Clone)]
struct TestStruct {
name: String,
age: u32,
active: bool,
score: f64,
tags: Vec<String>,
}
#[test]
fn test_from_element_value_roundtrip_struct() {
let original = TestStruct {
name: "test user".to_string(),
age: 42,
active: true,
score: 99.5,
tags: vec!["a".to_string(), "b".to_string()],
};
let ev = ElementValue::ComponentModel(original.clone().into_value());
let recovered = TestStruct::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
#[derive(IntoValue, FromValueAndType, PartialEq, Debug, Clone)]
struct NestedInner {
x: String,
y: u32,
}
#[derive(IntoValue, FromValueAndType, PartialEq, Debug, Clone)]
struct NestedOuter {
id: u64,
items: Vec<NestedInner>,
label: Option<String>,
}
#[test]
fn test_from_element_value_roundtrip_nested_struct() {
let original = NestedOuter {
id: 999,
items: vec![
NestedInner {
x: "first".to_string(),
y: 1,
},
NestedInner {
x: "second".to_string(),
y: 2,
},
],
label: Some("nested test".to_string()),
};
let ev = ElementValue::ComponentModel(original.clone().into_value());
let recovered = NestedOuter::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
#[derive(IntoValue, FromValueAndType, PartialEq, Debug, Clone)]
enum TestEnum {
A,
B(u32),
C { name: String, value: bool },
}
#[test]
fn test_from_element_value_roundtrip_enum() {
for original in [
TestEnum::A,
TestEnum::B(42),
TestEnum::C {
name: "test".to_string(),
value: true,
},
] {
let ev = ElementValue::ComponentModel(original.clone().into_value());
let recovered = TestEnum::from_element_value(ev).unwrap();
assert_eq!(recovered, original);
}
}
#[test]
fn test_from_element_value_roundtrip_result() {
let ok_val: Result<String, u32> = Ok("success".to_string());
let ev = ElementValue::ComponentModel(ok_val.clone().into_value());
let recovered = Result::<String, u32>::from_element_value(ev).unwrap();
assert_eq!(recovered, ok_val);
let err_val: Result<String, u32> = Err(404);
let ev = ElementValue::ComponentModel(err_val.clone().into_value());
let recovered = Result::<String, u32>::from_element_value(ev).unwrap();
assert_eq!(recovered, err_val);
}
#[derive(IntoValue, FromValueAndType, PartialEq, Debug, Clone)]
enum EnumCasePayloadNames {
Unit,
NamedRecord { name: String, value: bool },
MultiTuple(u32, u64),
}
#[test]
fn enum_case_payload_type_name_uses_case_name() {
let typ = <EnumCasePayloadNames as IntoValue>::get_type();
let root = typ.nodes.first().expect("missing root type node");
let golem_wasm::WitTypeNode::VariantType(cases) = &root.type_ else {
panic!("expected variant root node");
};
let named_record_idx = cases
.iter()
.find_map(|(name, idx)| (name == "NamedRecord").then_some(*idx))
.flatten()
.expect("missing NamedRecord case payload type");
let named_record_node = typ
.nodes
.get(named_record_idx as usize)
.expect("missing NamedRecord payload node");
assert_eq!(named_record_node.name.as_deref(), Some("NamedRecord"));
let multi_tuple_idx = cases
.iter()
.find_map(|(name, idx)| (name == "MultiTuple").then_some(*idx))
.flatten()
.expect("missing MultiTuple case payload type");
let multi_tuple_node = typ
.nodes
.get(multi_tuple_idx as usize)
.expect("missing MultiTuple payload node");
assert_eq!(multi_tuple_node.name.as_deref(), Some("MultiTuple"));
}
}