lmjtfy.git / apps / lmjtfy / src / ai.rs
ai.rsannotatedai.rssource43 lines · 1.7 KB · raw

The Workers AI binding, called with JSON text and answered with JSON text.

workers-rs's own Ai::run converts through serde-wasm-bindgen, which turns a JSON object into a JS Map unless every level is a Rust struct. The request here is built as JSON (the same text the page shows and the eval sends over REST), so it goes in through JSON.parse and the reply comes back through JSON.stringify, untouched either way.

9use js_sys::{Function, JSON, Promise, Reflect};
10use wasm_bindgen::{JsCast, JsValue};
11use wasm_bindgen_futures::JsFuture;
12use worker::Env;

The binding's name in wrangler.toml.

15const BINDING: &str = "AI";
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}

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}