objectiveai_wasm_js/lib.rs
1//! WebAssembly bindings for ObjectiveAI.
2//!
3//! This crate provides JavaScript/TypeScript bindings for client-side validation
4//! and compilation of ObjectiveAI types. It enables browser-based applications to:
5//!
6//! - Validate Ensemble LLM and Ensemble configurations
7//! - Compute content-addressed IDs (deterministic hashes)
8//! - Compile Function expressions for previewing during authoring
9//! - Compute prompt, tools, and response IDs for caching/deduplication
10//!
11//! # Usage
12//!
13//! This crate is compiled to WebAssembly and consumed via the `objectiveai` npm package.
14//! The TypeScript SDK wraps these functions with proper type definitions.
15//!
16//! # Functions
17//!
18//! - [`validateEnsembleLlm`] - Validate and compute ID for an Ensemble LLM
19//! - [`validateEnsemble`] - Validate and compute ID for an Ensemble
20//! - [`compileFunctionTasks`] - Compile function tasks for a given input
21//! - [`compileFunctionOutput`] - Compile function output from task results
22//! - [`promptId`] - Compute content-addressed ID for chat messages
23//! - [`toolsId`] - Compute content-addressed ID for tools
24//! - [`vectorResponseId`] - Compute content-addressed ID for a response option
25
26#![allow(non_snake_case)]
27use wasm_bindgen::prelude::*;
28
29/// Validates an Ensemble LLM configuration and computes its content-addressed ID.
30///
31/// Takes an Ensemble LLM definition, normalizes it (removes defaults, deduplicates),
32/// validates all fields, and computes a deterministic ID using XXHash3-128.
33///
34/// # Arguments
35///
36/// * `llm` - JavaScript object representing an Ensemble LLM configuration
37///
38/// # Returns
39///
40/// The validated Ensemble LLM with its computed `id` field populated.
41///
42/// # Errors
43///
44/// Returns an error string if validation fails (e.g., invalid model name,
45/// out-of-range parameters, conflicting settings).
46#[wasm_bindgen]
47pub fn validateEnsembleLlm(llm: JsValue) -> Result<JsValue, JsValue> {
48 // deserialize
49 let llm_base: objectiveai::ensemble_llm::EnsembleLlmBase =
50 serde_wasm_bindgen::from_value(llm)?;
51 // prepare, validate, and compute ID
52 let llm: objectiveai::ensemble_llm::EnsembleLlm = llm_base
53 .try_into()
54 .map_err(|e: String| JsValue::from_str(&e))?;
55 // serialize
56 let llm: JsValue = serde_wasm_bindgen::to_value(&llm)?;
57 Ok(llm)
58}
59
60/// Validates an Ensemble configuration and computes its content-addressed ID.
61///
62/// Takes an Ensemble definition (a collection of Ensemble LLMs), validates each
63/// LLM, and computes a deterministic ID for the ensemble as a whole.
64///
65/// # Arguments
66///
67/// * `ensemble` - JavaScript object representing an Ensemble configuration
68///
69/// # Returns
70///
71/// The validated Ensemble with its computed `id` field populated and all
72/// member LLMs validated with their IDs.
73///
74/// # Errors
75///
76/// Returns an error string if any LLM validation fails or the ensemble
77/// structure is invalid.
78#[wasm_bindgen]
79pub fn validateEnsemble(ensemble: JsValue) -> Result<JsValue, JsValue> {
80 // deserialize
81 let ensemble_base: objectiveai::ensemble::EnsembleBase =
82 serde_wasm_bindgen::from_value(ensemble)?;
83 // prepare, validate, and compute ID
84 let ensemble: objectiveai::ensemble::Ensemble = ensemble_base
85 .try_into()
86 .map_err(|e: String| JsValue::from_str(&e))?;
87 // serialize
88 let ensemble: JsValue = serde_wasm_bindgen::to_value(&ensemble)?;
89 Ok(ensemble)
90}
91
92/// Compiles a Function's task expressions for a given input.
93///
94/// Evaluates all JMESPath expressions in the function's tasks using the
95/// provided input data. This is used for previewing how tasks will be
96/// executed during Function authoring.
97///
98/// # Arguments
99///
100/// * `function` - JavaScript object representing a Function definition
101/// * `input` - JavaScript object representing the function input
102///
103/// # Returns
104///
105/// An array where each element corresponds to a task definition:
106/// - `null` if the task was skipped (skip expression evaluated to true)
107/// - `{ One: task }` for non-mapped tasks
108/// - `{ Many: [task, ...] }` for mapped tasks (expanded from input_maps)
109///
110/// # Errors
111///
112/// Returns an error string if expression evaluation fails or types don't match.
113#[wasm_bindgen]
114pub fn compileFunctionTasks(
115 function: JsValue,
116 input: JsValue,
117) -> Result<JsValue, JsValue> {
118 // deserialize
119 let function: objectiveai::functions::Function =
120 serde_wasm_bindgen::from_value(function)?;
121 let input: objectiveai::functions::expression::Input =
122 serde_wasm_bindgen::from_value(input)?;
123 // compile tasks
124 let tasks = function
125 .compile_tasks(&input)
126 .map_err(|e| JsValue::from_str(&e.to_string()))?;
127 // serialize
128 let tasks: JsValue = serde_wasm_bindgen::to_value(&tasks)?;
129 Ok(tasks)
130}
131
132/// Computes the final output of a Function given input and task results.
133///
134/// Evaluates the function's output expression using the provided input data
135/// and task outputs. Also validates that the output meets constraints:
136/// - Scalar functions: output must be in [0, 1]
137/// - Vector functions: output must sum to approximately 1
138///
139/// # Arguments
140///
141/// * `function` - JavaScript object representing a Function definition
142/// * `input` - JavaScript object representing the function input
143/// * `task_outputs` - Array of task outputs (from actual execution or mocked)
144///
145/// # Returns
146///
147/// An object with:
148/// - `output`: The computed scalar or vector output
149/// - `valid`: Boolean indicating if the output meets constraints
150///
151/// # Errors
152///
153/// Returns an error string if expression evaluation fails.
154#[wasm_bindgen]
155pub fn compileFunctionOutput(
156 function: JsValue,
157 input: JsValue,
158 task_outputs: JsValue,
159) -> Result<JsValue, JsValue> {
160 // deserialize
161 let function: objectiveai::functions::Function =
162 serde_wasm_bindgen::from_value(function)?;
163 let input: objectiveai::functions::expression::Input =
164 serde_wasm_bindgen::from_value(input)?;
165 let task_outputs: Vec<
166 Option<objectiveai::functions::expression::TaskOutput<'static>>,
167 > = serde_wasm_bindgen::from_value(task_outputs)?;
168 // compile output
169 let output = function
170 .compile_output(&input, &task_outputs)
171 .map_err(|e| JsValue::from_str(&e.to_string()))?;
172 // serialize
173 let output: JsValue = serde_wasm_bindgen::to_value(&output)?;
174 Ok(output)
175}
176
177/// Computes a content-addressed ID for chat messages.
178///
179/// Normalizes the messages (consolidates text parts, removes empty content)
180/// and computes a deterministic hash. This ID is used for caching and
181/// deduplicating requests with identical prompts.
182///
183/// # Arguments
184///
185/// * `prompt` - Array of chat messages
186///
187/// # Returns
188///
189/// A base62-encoded hash string uniquely identifying the prompt content.
190///
191/// # Errors
192///
193/// Returns an error if the messages cannot be deserialized.
194#[wasm_bindgen]
195pub fn promptId(prompt: JsValue) -> Result<String, JsValue> {
196 // deserialize
197 let mut prompt: Vec<objectiveai::chat::completions::request::Message> =
198 serde_wasm_bindgen::from_value(prompt)?;
199 // prepare and compute ID
200 objectiveai::chat::completions::request::prompt::prepare(&mut prompt);
201 let id = objectiveai::chat::completions::request::prompt::id(&prompt);
202 Ok(id)
203}
204
205/// Computes a content-addressed ID for a tools array.
206///
207/// Computes a deterministic hash for the tools configuration. This ID is
208/// used for caching and deduplicating requests with identical tool sets.
209///
210/// # Arguments
211///
212/// * `tools` - Array of tool definitions
213///
214/// # Returns
215///
216/// A base62-encoded hash string uniquely identifying the tools.
217///
218/// # Errors
219///
220/// Returns an error if the tools cannot be deserialized.
221#[wasm_bindgen]
222pub fn toolsId(tools: JsValue) -> Result<String, JsValue> {
223 // deserialize
224 let tools: Vec<objectiveai::chat::completions::request::Tool> =
225 serde_wasm_bindgen::from_value(tools)?;
226 // compute ID
227 let id = objectiveai::chat::completions::request::tools::id(&tools);
228 Ok(id)
229}
230
231/// Computes a content-addressed ID for a vector completion response option.
232///
233/// Normalizes the response content (consolidates text parts, removes empty
234/// content) and computes a deterministic hash. This ID is used for caching
235/// and identifying individual response options in vector completions.
236///
237/// # Arguments
238///
239/// * `response` - A rich content object (text or multipart content)
240///
241/// # Returns
242///
243/// A base62-encoded hash string uniquely identifying the response content.
244///
245/// # Errors
246///
247/// Returns an error if the response cannot be deserialized.
248#[wasm_bindgen]
249pub fn vectorResponseId(response: JsValue) -> Result<String, JsValue> {
250 // deserialize
251 let mut response: objectiveai::chat::completions::request::RichContent =
252 serde_wasm_bindgen::from_value(response)?;
253 // prepare and compute ID
254 response.prepare();
255 let id = response.id();
256 Ok(id)
257}