1//! The Workers AI binding, called with JSON text and answered with JSON text. 2//! 3//! workers-rs's own `Ai::run` converts through serde-wasm-bindgen, which 4//! turns a JSON object into a JS `Map` unless every level is a Rust struct. 5//! The request here is built as JSON (the same text the page shows and the 6//! eval sends over REST), so it goes in through `JSON.parse` and the reply 7//! comes back through `JSON.stringify`, untouched either way. 8 9use js_sys::{Function, JSON, Promise, Reflect}; 10use wasm_bindgen::{JsCast, JsValue}; 11use wasm_bindgen_futures::JsFuture; 12use worker::Env; 13 14/// The binding's name in `wrangler.toml`. 15const BINDING: &str = "AI"; 16 17fn message(error: JsValue) -> String { 18 error 19 .dyn_ref::<js_sys::Error>() 20 .map(|error| String::from(error.message())) 21 .or_else(|| error.as_string()) 22 .unwrap_or_else(|| format!("{error:?}")) 23} 24 25/// Runs `model` on `request` and returns the reply as it came. 26pub async fn run(env: &Env, model: &str, request: &str) -> Result<String, String> { 27 let ai = Reflect::get(env.as_ref(), &BINDING.into()).map_err(message)?; 28 if ai.is_undefined() { 29 return Err(format!("the Worker has no {BINDING} binding")); 30 } 31 let run: Function = Reflect::get(&ai, &"run".into()) 32 .map_err(message)? 33 .dyn_into() 34 .map_err(|_| format!("{BINDING}.run is not a function"))?; 35 let input = JSON::parse(request).map_err(message)?; 36 let promise: Promise = run 37 .call2(&ai, &model.into(), &input) 38 .map_err(message)? 39 .dyn_into() 40 .map_err(|_| format!("{BINDING}.run did not return a promise"))?; 41 let reply = JsFuture::from(promise).await.map_err(message)?; 42 JSON::stringify(&reply).map(String::from).map_err(message) 43}