1use schemars::JsonSchema;
8use serde::{Serialize, de::DeserializeOwned};
9use serde_json::{Value, json};
10
11#[cfg(target_arch = "wasm32")]
12extern crate self as lenso_plugin_sdk;
13
14#[cfg(target_arch = "wasm32")]
15#[doc(hidden)]
16pub mod __wasm {
17 wit_bindgen::generate!({
18 inline: r#"
19 package lenso:runtime@1.0.0;
20
21 world plugin {
22 export describe: func() -> string;
23 export invoke: func(capability: string, operation: string, request-json: string) -> result<string, string>;
24 }
25 "#,
26 world: "plugin",
27 export_macro_name: "export_lenso_plugin",
28 pub_export_macro: true,
29 default_bindings_module: "::lenso_plugin_sdk::__wasm",
30 });
31}
32
33#[derive(Clone, Debug)]
35pub enum RequestOutcome {
36 Success(Value),
38 DomainError(Value),
40 Failure(String),
42}
43
44pub trait RequestPlugin: Default {
46 fn invoke(&self, capability: &str, operation: &str, request: Value) -> RequestOutcome;
48}
49
50pub trait AgentTool: Default {
52 type Arguments: DeserializeOwned + JsonSchema;
54 type Error: Serialize;
56
57 const NAME: &'static str;
59 const DESCRIPTION: &'static str;
61
62 fn execute(&self, arguments: Self::Arguments) -> Result<String, Self::Error>;
64}
65
66#[doc(hidden)]
67pub struct AgentToolAdapter<T>(T);
68
69impl<T> std::fmt::Debug for AgentToolAdapter<T> {
70 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 formatter.write_str("AgentToolAdapter")
72 }
73}
74
75impl<T: AgentTool> Default for AgentToolAdapter<T> {
76 fn default() -> Self {
77 Self(T::default())
78 }
79}
80
81impl<T: AgentTool> RequestPlugin for AgentToolAdapter<T> {
82 fn invoke(&self, capability: &str, operation: &str, request: Value) -> RequestOutcome {
83 if capability != "lenso.agent.tool-provider@2" {
84 return RequestOutcome::DomainError(json!("not_found"));
85 }
86 match operation {
87 "catalog" => catalog::<T>(),
88 "execute" => execute(&self.0, &request),
89 _ => RequestOutcome::DomainError(json!("not_found")),
90 }
91 }
92}
93
94fn catalog<T: AgentTool>() -> RequestOutcome {
95 let input_schema_json = match serde_json::to_string(&schemars::schema_for!(T::Arguments)) {
96 Ok(schema) => schema,
97 Err(error) => return RequestOutcome::Failure(error.to_string()),
98 };
99 RequestOutcome::Success(json!({
100 "tools": [{
101 "name": T::NAME,
102 "description": T::DESCRIPTION,
103 "input_schema_json": input_schema_json,
104 "execution": "parallel_safe",
105 }],
106 }))
107}
108
109fn execute<T: AgentTool>(tool: &T, request: &Value) -> RequestOutcome {
110 let Some(name) = request.get("name").and_then(Value::as_str) else {
111 return RequestOutcome::DomainError(json!("invalid_arguments"));
112 };
113 let Some(arguments_json) = request.get("arguments_json").and_then(Value::as_str) else {
114 return RequestOutcome::DomainError(json!("invalid_arguments"));
115 };
116 if name != T::NAME {
117 return RequestOutcome::DomainError(json!("not_found"));
118 }
119 let Ok(arguments) = serde_json::from_str::<T::Arguments>(arguments_json) else {
120 return RequestOutcome::DomainError(json!("invalid_arguments"));
121 };
122 match tool.execute(arguments) {
123 Ok(content) => RequestOutcome::Success(json!({
124 "content_type": "text",
125 "content": content,
126 "metadata_json": r#"{"provider":"lenso-portable"}"#,
127 })),
128 Err(error) => match serde_json::to_value(error) {
129 Ok(error) => RequestOutcome::DomainError(error),
130 Err(error) => RequestOutcome::Failure(error.to_string()),
131 },
132 }
133}
134
135#[macro_export]
138macro_rules! export_request_plugin {
139 (
140 $plugin:ty {
141 capability_id: $capability_id:literal,
142 descriptor_version: $descriptor_version:literal,
143 requests: [$first_request:literal $(, $request:literal)* $(,)?] $(,)?
144 }
145 ) => {
146 type __LensoExportedPlugin = $plugin;
147
148 #[cfg(target_arch = "wasm32")]
149 mod __lenso_wasm_export {
150 struct Component;
151
152 const DESCRIPTOR: &str = $crate::__private::lenso_guest_sdk::__request_plugin_descriptor!(
153 $capability_id,
154 $descriptor_version,
155 $first_request $(, $request)*
156 );
157
158 #[used]
159 #[unsafe(link_section = "lenso.plugin-descriptor.v1")]
160 static DESCRIPTOR_SECTION: [u8; DESCRIPTOR.len()] =
161 $crate::__private::lenso_guest_sdk::__descriptor_bytes(DESCRIPTOR);
162
163 impl $crate::__private::wasm::Guest for Component {
164 fn describe() -> ::std::string::String {
165 DESCRIPTOR.to_owned()
166 }
167
168 fn invoke(
169 capability: ::std::string::String,
170 operation: ::std::string::String,
171 request_json: ::std::string::String,
172 ) -> ::std::result::Result<::std::string::String, ::std::string::String> {
173 let request = $crate::__private::serde_json::from_str(&request_json)
174 .map_err(|_| "\"invalid_arguments\"".to_owned())?;
175 match <super::__LensoExportedPlugin as $crate::RequestPlugin>::invoke(
176 &<super::__LensoExportedPlugin as ::std::default::Default>::default(),
177 &capability,
178 &operation,
179 request,
180 ) {
181 $crate::RequestOutcome::Success(value) =>
182 $crate::__private::serde_json::to_string(&value)
183 .map_err(|error| error.to_string()),
184 $crate::RequestOutcome::DomainError(value) =>
185 ::std::result::Result::Err(
186 $crate::__private::serde_json::to_string(&value)
187 .unwrap_or_else(|_| "\"execution_failed\"".to_owned()),
188 ),
189 $crate::RequestOutcome::Failure(detail) =>
190 ::std::result::Result::Err(
191 $crate::__private::serde_json::to_string(&detail)
192 .unwrap_or_else(|_| "\"execution_failed\"".to_owned()),
193 ),
194 }
195 }
196 }
197
198 $crate::__private::wasm::export_lenso_plugin!(Component);
199 }
200
201 #[cfg(not(target_arch = "wasm32"))]
202 mod __lenso_process_export {
203 struct Component(super::__LensoExportedPlugin);
204
205 impl $crate::__private::lenso_process_sdk::ProcessPlugin for Component {
206 fn descriptor(&self) -> $crate::__private::serde_json::Value {
207 $crate::__private::serde_json::json!({
208 "abi": "lenso.json-request@1",
209 "capabilities": [{
210 "capability_id": $capability_id,
211 "descriptor_version": $descriptor_version,
212 "request_operations": [$first_request $(, $request)*],
213 }],
214 })
215 }
216
217 fn invoke(
218 &self,
219 capability: &str,
220 operation: &str,
221 request: $crate::__private::serde_json::Value,
222 ) -> $crate::__private::lenso_process_sdk::ProcessOutcome {
223 match <super::__LensoExportedPlugin as $crate::RequestPlugin>::invoke(
224 &self.0,
225 capability,
226 operation,
227 request,
228 ) {
229 $crate::RequestOutcome::Success(value) =>
230 $crate::__private::lenso_process_sdk::ProcessOutcome::Success(value),
231 $crate::RequestOutcome::DomainError(value) =>
232 $crate::__private::lenso_process_sdk::ProcessOutcome::DomainError(value),
233 $crate::RequestOutcome::Failure(detail) =>
234 $crate::__private::lenso_process_sdk::ProcessOutcome::Failure(detail),
235 }
236 }
237 }
238
239 pub fn serve() {
240 $crate::__private::lenso_process_sdk::serve(&Component(
241 <super::__LensoExportedPlugin as ::std::default::Default>::default(),
242 ))
243 .expect("serve Lenso Process Plugin");
244 }
245 }
246
247 #[cfg(not(target_arch = "wasm32"))]
248 fn main() {
249 __lenso_process_export::serve();
250 }
251 };
252}
253
254#[macro_export]
256macro_rules! export_agent_tool {
257 ($plugin:ty $(,)?) => {
258 $crate::export_request_plugin! {
259 $crate::AgentToolAdapter<$plugin> {
260 capability_id: "lenso.agent.tool-provider@2",
261 descriptor_version: "2.0.0",
262 requests: ["catalog", "execute"],
263 }
264 }
265 };
266}
267
268#[doc(hidden)]
269pub mod __private {
270 pub use serde_json;
271
272 #[cfg(target_arch = "wasm32")]
273 pub use crate::__wasm as wasm;
274 #[cfg(target_arch = "wasm32")]
275 pub use lenso_guest_sdk;
276 #[cfg(not(target_arch = "wasm32"))]
277 pub use lenso_process_sdk;
278}