1#![cfg_attr(target_arch = "wasm32", no_std)]
2
3#[cfg(target_arch = "wasm32")]
4extern crate alloc;
5
6mod data;
7
8#[cfg(target_arch = "wasm32")]
9use alloc::{
10 format,
11 string::{String, ToString},
12 vec::Vec,
13};
14#[cfg(not(target_arch = "wasm32"))]
15use greentic_interfaces_host::bindings::exports::greentic::interfaces_pack::component_api::ProviderMeta;
16#[cfg(not(target_arch = "wasm32"))]
17const _: fn(ProviderMeta) = |_meta| {};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20#[cfg(not(target_arch = "wasm32"))]
21use std::vec::Vec;
22
23#[derive(Debug, Clone, Serialize)]
24pub struct FlowInfo {
25 pub id: String,
26 pub human_name: Option<String>,
27 pub description: Option<String>,
28}
29
30#[derive(Debug, Clone, Serialize)]
31pub struct SchemaDoc {
32 pub flow_id: String,
33 pub schema_json: serde_json::Value,
34}
35
36#[derive(Debug, Clone, Serialize)]
37pub struct PrepareResult {
38 pub status: String,
39 pub error: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize)]
43pub struct RunResult {
44 pub status: String,
45 pub output: Option<serde_json::Value>,
46 pub error: Option<String>,
47}
48
49#[derive(Debug, Clone, Serialize)]
50pub struct A2AItem {
51 pub title: String,
52 pub flow_id: String,
53}
54
55pub trait PackExport {
56 fn list_flows(&self) -> Vec<FlowInfo>;
57 fn get_flow_schema(&self, flow_id: &str) -> Option<SchemaDoc>;
58 fn prepare_flow(&self, flow_id: &str) -> PrepareResult;
59 fn run_flow(&self, flow_id: &str, input: serde_json::Value) -> RunResult;
60 fn a2a_search(&self, query: &str) -> Vec<A2AItem>;
61}
62
63pub fn manifest_cbor() -> &'static [u8] {
65 data::MANIFEST_CBOR
66}
67
68pub fn manifest_value() -> Value {
70 serde_cbor::from_slice(data::MANIFEST_CBOR)
71 .expect("generated manifest bytes should always be valid CBOR")
72}
73
74pub fn manifest_as<T>() -> T
76where
77 T: for<'de> Deserialize<'de>,
78{
79 serde_cbor::from_slice(data::MANIFEST_CBOR)
80 .expect("generated manifest matches the requested type")
81}
82
83pub fn flows() -> &'static [(&'static str, &'static str)] {
85 data::FLOWS
86}
87
88pub fn templates() -> &'static [(&'static str, &'static [u8])] {
90 data::TEMPLATES
91}
92
93pub fn template_by_path(path: &str) -> Option<&'static [u8]> {
95 data::TEMPLATES
96 .iter()
97 .find(|(logical, _)| *logical == path)
98 .map(|(_, bytes)| *bytes)
99}
100
101#[derive(Debug, Default)]
103pub struct Component;
104
105impl PackExport for Component {
106 fn list_flows(&self) -> Vec<FlowInfo> {
107 flows()
108 .iter()
109 .map(|(id, _)| FlowInfo {
110 id: (*id).to_string(),
111 human_name: None,
112 description: None,
113 })
114 .collect()
115 }
116
117 fn get_flow_schema(&self, flow_id: &str) -> Option<SchemaDoc> {
118 flows()
119 .iter()
120 .find(|(id, _)| *id == flow_id)
121 .map(|(id, _)| SchemaDoc {
122 flow_id: (*id).to_string(),
123 schema_json: serde_json::json!({}),
124 })
125 }
126
127 fn prepare_flow(&self, flow_id: &str) -> PrepareResult {
128 if flows().iter().any(|(id, _)| *id == flow_id) {
129 PrepareResult {
130 status: "ok".into(),
131 error: None,
132 }
133 } else {
134 PrepareResult {
135 status: "error".into(),
136 error: Some(format!("unknown flow: {flow_id}")),
137 }
138 }
139 }
140
141 fn run_flow(&self, flow_id: &str, input: Value) -> RunResult {
142 if let Some((_, source)) = flows().iter().find(|(id, _)| *id == flow_id) {
143 RunResult {
144 status: "ok".into(),
145 output: Some(serde_json::json!({
146 "flow_id": flow_id,
147 "source": source,
148 "input_echo": input,
149 })),
150 error: None,
151 }
152 } else {
153 RunResult {
154 status: "error".into(),
155 output: None,
156 error: Some(format!("unknown flow: {flow_id}")),
157 }
158 }
159 }
160
161 fn a2a_search(&self, _query: &str) -> Vec<A2AItem> {
162 Vec::new()
163 }
164}
165
166pub fn component() -> Component {
168 Component
169}
170
171#[unsafe(no_mangle)]
174pub extern "C" fn greentic_pack_export__list_flows(json_buffer: *mut u8, len: usize) -> usize {
175 let component = Component;
176 let flows = component.list_flows();
177 write_json_response(&flows, json_buffer, len)
178}
179
180#[unsafe(no_mangle)]
181pub unsafe extern "C" fn greentic_pack_export__prepare_flow(
187 flow_id_ptr: *const u8,
188 flow_id_len: usize,
189 json_buffer: *mut u8,
190 len: usize,
191) -> usize {
192 let component = Component;
193 let flow_id = unsafe { slice_to_str(flow_id_ptr, flow_id_len) };
194 let result = component.prepare_flow(flow_id);
195 write_json_response(&result, json_buffer, len)
196}
197
198#[unsafe(no_mangle)]
199pub unsafe extern "C" fn greentic_pack_export__run_flow(
205 flow_id_ptr: *const u8,
206 flow_id_len: usize,
207 json_buffer: *mut u8,
208 len: usize,
209) -> usize {
210 let component = Component;
211 let flow_id = unsafe { slice_to_str(flow_id_ptr, flow_id_len) };
212 let result = component.run_flow(flow_id, serde_json::Value::Null);
213 write_json_response(&result, json_buffer, len)
214}
215
216#[unsafe(no_mangle)]
217pub extern "C" fn greentic_pack_export__a2a_search(json_buffer: *mut u8, len: usize) -> usize {
218 let component = Component;
219 let items = component.a2a_search("");
220 write_json_response(&items, json_buffer, len)
221}
222
223fn write_json_response<T: serde::Serialize>(value: &T, buffer: *mut u8, len: usize) -> usize {
224 let json = serde_json::to_vec(value).expect("serialisation succeeds");
225 if buffer.is_null() || len == 0 {
226 return json.len();
227 }
228
229 let copy_len = core::cmp::min(json.len(), len);
230 unsafe {
231 core::ptr::copy_nonoverlapping(json.as_ptr(), buffer, copy_len);
232 }
233 copy_len
234}
235
236unsafe fn slice_to_str<'a>(ptr: *const u8, len: usize) -> &'a str {
237 let bytes = unsafe { core::slice::from_raw_parts(ptr, len) };
238 core::str::from_utf8(bytes).expect("flow id is valid utf-8")
239}