subako_function_sdk/
package.rs1use std::io::Write as _;
5use std::process::ExitCode;
6
7use schemars::JsonSchema;
8use schemars::generate::{Contract, SchemaSettings};
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12use crate::{Input, Output, Reply, Server, ServerInfo, ToolError};
13
14pub const MANIFEST_FLAG: &str = "--subako-manifest";
16
17pub struct Function {
19 name: String,
20 description: String,
21 input_schema: fn(Contract) -> serde_json::Value,
22 output_schema: fn(Contract) -> serde_json::Value,
23 call: Box<dyn Fn(Input) -> Result<Output, ToolError>>,
24}
25
26impl Function {
27 pub fn new<I, O, E>(
29 name: impl Into<String>,
30 description: impl Into<String>,
31 f: impl Fn(I) -> Result<O, E> + 'static,
32 ) -> Self
33 where
34 I: DeserializeOwned + JsonSchema,
35 O: Reply,
36 E: Into<ToolError>,
37 {
38 Function {
39 name: name.into(),
40 description: description.into(),
41 input_schema: schema::<I>,
42 output_schema: O::schema,
43 call: Box::new(move |input| f(input.parse()?).map_err(Into::into)?.into_output()),
44 }
45 }
46
47 pub fn name(&self) -> &str {
48 &self.name
49 }
50}
51
52pub const ABI: u32 = 1;
55
56#[derive(Debug, Clone, PartialEq, Serialize)]
59pub struct Manifest {
60 pub abi: u32,
61 pub name: String,
62 pub description: String,
63}
64
65pub struct Package {
67 name: String,
68 version: String,
69 description: String,
70 functions: Vec<Function>,
71}
72
73impl Package {
74 pub fn new(
75 name: impl Into<String>,
76 description: impl Into<String>,
77 functions: Vec<Function>,
78 ) -> Self {
79 Package {
80 name: name.into(),
81 version: "0.0.0".to_string(),
82 description: description.into(),
83 functions,
84 }
85 }
86
87 pub fn with_version(self, version: impl Into<String>) -> Self {
90 Package {
91 version: version.into(),
92 ..self
93 }
94 }
95
96 pub fn functions(&self) -> &[Function] {
97 &self.functions
98 }
99
100 pub fn manifest(&self) -> Manifest {
101 Manifest {
102 abi: ABI,
103 name: self.name.clone(),
104 description: self.description.clone(),
105 }
106 }
107
108 pub fn call(&self, tool: &str, input: Input) -> Result<Output, ToolError> {
110 match self.functions.iter().find(|f| f.name == tool) {
111 Some(function) => (function.call)(input),
112 None => Err(ToolError::unknown_tool(tool)),
113 }
114 }
115
116 pub fn execute(&self, args: &[String], stdin: &[u8]) -> (Vec<u8>, u8) {
119 match args.get(1).map(String::as_str) {
120 Some(MANIFEST_FLAG) => (self.manifest_json(), 0),
121 _ => crate::execute(stdin, self),
122 }
123 }
124
125 pub fn run(&self) -> ExitCode {
129 let args: Vec<String> = std::env::args().collect();
130 match args.get(1).map(String::as_str) {
131 Some(MANIFEST_FLAG) => {
132 let mut stdout = std::io::stdout().lock();
133 match stdout
134 .write_all(&self.manifest_json())
135 .and_then(|()| stdout.flush())
136 {
137 Ok(()) => ExitCode::SUCCESS,
138 Err(_) => ExitCode::from(2),
139 }
140 }
141 _ => crate::run(self),
142 }
143 }
144
145 fn manifest_json(&self) -> Vec<u8> {
146 let mut json = serde_json::to_vec_pretty(&self.manifest()).unwrap_or_default();
148 json.push(b'\n');
149 json
150 }
151}
152
153impl Server for Package {
154 fn info(&self) -> ServerInfo<'_> {
155 ServerInfo {
156 name: &self.name,
157 version: &self.version,
158 }
159 }
160
161 fn tools(&self) -> Vec<serde_json::Value> {
162 self.functions
163 .iter()
164 .map(|f| {
165 serde_json::json!({
166 "name": f.name,
167 "description": f.description,
168 "inputSchema": (f.input_schema)(Contract::Deserialize),
169 "outputSchema": (f.output_schema)(Contract::Serialize),
170 })
171 })
172 .collect()
173 }
174
175 fn call(&self, tool: &str, input: Input) -> Result<Output, ToolError> {
176 Package::call(self, tool, input)
177 }
178}
179
180pub(crate) fn schema<T: JsonSchema>(contract: Contract) -> serde_json::Value {
183 let mut schema = SchemaSettings::draft2020_12()
184 .with(|s| {
185 s.inline_subschemas = true;
186 s.meta_schema = None;
187 s.contract = contract;
188 })
189 .into_generator()
190 .into_root_schema_for::<T>();
191 schema.remove("title");
192 schema.to_value()
193}