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, 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: Serialize + JsonSchema,
36 E: Into<ToolError>,
37 {
38 Function {
39 name: name.into(),
40 description: description.into(),
41 input_schema: schema::<I>,
42 output_schema: schema::<O>,
43 call: Box::new(move |input| Output::json(&f(input.parse()?).map_err(Into::into)?)),
44 }
45 }
46
47 pub fn name(&self) -> &str {
48 &self.name
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize)]
54pub struct Manifest {
55 pub name: String,
56 pub description: String,
57 pub functions: Vec<ManifestFunction>,
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize)]
61pub struct ManifestFunction {
62 pub name: String,
63 pub description: String,
64 pub input_schema: serde_json::Value,
65 pub output_schema: serde_json::Value,
66}
67
68pub struct Package {
70 name: String,
71 description: String,
72 functions: Vec<Function>,
73}
74
75impl Package {
76 pub fn new(
77 name: impl Into<String>,
78 description: impl Into<String>,
79 functions: Vec<Function>,
80 ) -> Self {
81 Package {
82 name: name.into(),
83 description: description.into(),
84 functions,
85 }
86 }
87
88 pub fn functions(&self) -> &[Function] {
89 &self.functions
90 }
91
92 pub fn manifest(&self) -> Manifest {
93 Manifest {
94 name: self.name.clone(),
95 description: self.description.clone(),
96 functions: self
97 .functions
98 .iter()
99 .map(|f| ManifestFunction {
100 name: f.name.clone(),
101 description: f.description.clone(),
102 input_schema: (f.input_schema)(Contract::Deserialize),
103 output_schema: (f.output_schema)(Contract::Serialize),
104 })
105 .collect(),
106 }
107 }
108
109 pub fn call(&self, tool: &str, input: Input) -> Result<Output, ToolError> {
111 match self.functions.iter().find(|f| f.name == tool) {
112 Some(function) => (function.call)(input),
113 None => Err(ToolError::unknown_tool(tool)),
114 }
115 }
116
117 pub fn execute(&self, args: &[String], stdin: &[u8]) -> (Vec<u8>, u8) {
120 match args.get(1).map(String::as_str) {
121 Some(MANIFEST_FLAG) => (self.manifest_json(), 0),
122 _ => crate::execute(args, stdin, |tool, input| self.call(tool, input)),
123 }
124 }
125
126 pub fn run(&self) -> ExitCode {
130 let args: Vec<String> = std::env::args().collect();
131 match args.get(1).map(String::as_str) {
132 Some(MANIFEST_FLAG) => {
133 let mut stdout = std::io::stdout().lock();
134 match stdout
135 .write_all(&self.manifest_json())
136 .and_then(|()| stdout.flush())
137 {
138 Ok(()) => ExitCode::SUCCESS,
139 Err(_) => ExitCode::from(2),
140 }
141 }
142 _ => crate::run(|tool, input| self.call(tool, input)),
143 }
144 }
145
146 fn manifest_json(&self) -> Vec<u8> {
147 let mut json = serde_json::to_vec_pretty(&self.manifest()).unwrap_or_default();
149 json.push(b'\n');
150 json
151 }
152}
153
154fn schema<T: JsonSchema>(contract: Contract) -> serde_json::Value {
157 let mut schema = SchemaSettings::draft2020_12()
158 .with(|s| {
159 s.inline_subschemas = true;
160 s.meta_schema = None;
161 s.contract = contract;
162 })
163 .into_generator()
164 .into_root_schema_for::<T>();
165 schema.remove("title");
166 schema.to_value()
167}