simple-agents-wasm 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/index.d.ts +26 -2
- package/index.js +59 -1158
- package/package.json +1 -1
- package/pkg/simple_agents_wasm.d.ts +1 -3
- package/pkg/simple_agents_wasm.js +18 -18
- package/pkg/simple_agents_wasm_bg.wasm +0 -0
- package/pkg/simple_agents_wasm_bg.wasm.d.ts +1 -1
- package/runtime/rust-runtime.js +21 -3
- package/rust/Cargo.toml +1 -1
- package/rust/src/lib.rs +212 -60
package/package.json
CHANGED
|
@@ -24,15 +24,12 @@ export class WasmClient {
|
|
|
24
24
|
|
|
25
25
|
export function supportsRustWasm(): boolean;
|
|
26
26
|
|
|
27
|
-
export function toJsArray(value: any): Array<any>;
|
|
28
|
-
|
|
29
27
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
30
28
|
|
|
31
29
|
export interface InitOutput {
|
|
32
30
|
readonly memory: WebAssembly.Memory;
|
|
33
31
|
readonly __wbg_wasmclient_free: (a: number, b: number) => void;
|
|
34
32
|
readonly supportsRustWasm: () => number;
|
|
35
|
-
readonly toJsArray: (a: any) => any;
|
|
36
33
|
readonly wasmclient_complete: (a: number, b: number, c: number, d: any, e: number) => any;
|
|
37
34
|
readonly wasmclient_new: (a: number, b: number, c: any) => [number, number, number];
|
|
38
35
|
readonly wasmclient_runWorkflowYaml: (a: number, b: number, c: number, d: any) => [number, number, number];
|
|
@@ -41,6 +38,7 @@ export interface InitOutput {
|
|
|
41
38
|
readonly wasmclient_streamEvents: (a: number, b: number, c: number, d: any, e: any, f: number) => any;
|
|
42
39
|
readonly wasm_bindgen__convert__closures_____invoke__h9f53f643b01d7c8e: (a: number, b: number, c: any) => [number, number];
|
|
43
40
|
readonly wasm_bindgen__convert__closures_____invoke__h05acb8c479b21d4b: (a: number, b: number, c: any, d: any) => void;
|
|
41
|
+
readonly wasm_bindgen__convert__closures_____invoke__h25554e005bd8517d: (a: number, b: number, c: any) => void;
|
|
44
42
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
45
43
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
46
44
|
readonly __wbindgen_exn_store: (a: number) => void;
|
|
@@ -108,15 +108,6 @@ export function supportsRustWasm() {
|
|
|
108
108
|
return ret !== 0;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/**
|
|
112
|
-
* @param {any} value
|
|
113
|
-
* @returns {Array<any>}
|
|
114
|
-
*/
|
|
115
|
-
export function toJsArray(value) {
|
|
116
|
-
const ret = wasm.toJsArray(value);
|
|
117
|
-
return ret;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
111
|
function __wbg_get_imports() {
|
|
121
112
|
const import0 = {
|
|
122
113
|
__proto__: null,
|
|
@@ -357,13 +348,13 @@ function __wbg_get_imports() {
|
|
|
357
348
|
const ret = arg0.next();
|
|
358
349
|
return ret;
|
|
359
350
|
}, arguments); },
|
|
351
|
+
__wbg_parse_545d11396395fbbd: function() { return handleError(function (arg0, arg1) {
|
|
352
|
+
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
|
353
|
+
return ret;
|
|
354
|
+
}, arguments); },
|
|
360
355
|
__wbg_prototypesetcall_3e05eb9545565046: function(arg0, arg1, arg2) {
|
|
361
356
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
362
357
|
},
|
|
363
|
-
__wbg_push_6bdbc990be5ac37b: function(arg0, arg1) {
|
|
364
|
-
const ret = arg0.push(arg1);
|
|
365
|
-
return ret;
|
|
366
|
-
},
|
|
367
358
|
__wbg_queueMicrotask_abaf92f0bd4e80a4: function(arg0) {
|
|
368
359
|
const ret = arg0.queueMicrotask;
|
|
369
360
|
return ret;
|
|
@@ -418,26 +409,31 @@ function __wbg_get_imports() {
|
|
|
418
409
|
return ret;
|
|
419
410
|
},
|
|
420
411
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
421
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
412
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 157, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
422
413
|
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h9f53f643b01d7c8e);
|
|
423
414
|
return ret;
|
|
424
415
|
},
|
|
425
|
-
__wbindgen_cast_0000000000000002: function(arg0) {
|
|
416
|
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
417
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 78, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
418
|
+
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h25554e005bd8517d);
|
|
419
|
+
return ret;
|
|
420
|
+
},
|
|
421
|
+
__wbindgen_cast_0000000000000003: function(arg0) {
|
|
426
422
|
// Cast intrinsic for `F64 -> Externref`.
|
|
427
423
|
const ret = arg0;
|
|
428
424
|
return ret;
|
|
429
425
|
},
|
|
430
|
-
|
|
426
|
+
__wbindgen_cast_0000000000000004: function(arg0) {
|
|
431
427
|
// Cast intrinsic for `I64 -> Externref`.
|
|
432
428
|
const ret = arg0;
|
|
433
429
|
return ret;
|
|
434
430
|
},
|
|
435
|
-
|
|
431
|
+
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
|
436
432
|
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
437
433
|
const ret = getStringFromWasm0(arg0, arg1);
|
|
438
434
|
return ret;
|
|
439
435
|
},
|
|
440
|
-
|
|
436
|
+
__wbindgen_cast_0000000000000006: function(arg0) {
|
|
441
437
|
// Cast intrinsic for `U64 -> Externref`.
|
|
442
438
|
const ret = BigInt.asUintN(64, arg0);
|
|
443
439
|
return ret;
|
|
@@ -458,6 +454,10 @@ function __wbg_get_imports() {
|
|
|
458
454
|
};
|
|
459
455
|
}
|
|
460
456
|
|
|
457
|
+
function wasm_bindgen__convert__closures_____invoke__h25554e005bd8517d(arg0, arg1, arg2) {
|
|
458
|
+
wasm.wasm_bindgen__convert__closures_____invoke__h25554e005bd8517d(arg0, arg1, arg2);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
461
|
function wasm_bindgen__convert__closures_____invoke__h9f53f643b01d7c8e(arg0, arg1, arg2) {
|
|
462
462
|
const ret = wasm.wasm_bindgen__convert__closures_____invoke__h9f53f643b01d7c8e(arg0, arg1, arg2);
|
|
463
463
|
if (ret[1]) {
|
|
Binary file
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const __wbg_wasmclient_free: (a: number, b: number) => void;
|
|
5
5
|
export const supportsRustWasm: () => number;
|
|
6
|
-
export const toJsArray: (a: any) => any;
|
|
7
6
|
export const wasmclient_complete: (a: number, b: number, c: number, d: any, e: number) => any;
|
|
8
7
|
export const wasmclient_new: (a: number, b: number, c: any) => [number, number, number];
|
|
9
8
|
export const wasmclient_runWorkflowYaml: (a: number, b: number, c: number, d: any) => [number, number, number];
|
|
@@ -12,6 +11,7 @@ export const wasmclient_runYamlString: (a: number, b: number, c: number, d: any,
|
|
|
12
11
|
export const wasmclient_streamEvents: (a: number, b: number, c: number, d: any, e: any, f: number) => any;
|
|
13
12
|
export const wasm_bindgen__convert__closures_____invoke__h9f53f643b01d7c8e: (a: number, b: number, c: any) => [number, number];
|
|
14
13
|
export const wasm_bindgen__convert__closures_____invoke__h05acb8c479b21d4b: (a: number, b: number, c: any, d: any) => void;
|
|
14
|
+
export const wasm_bindgen__convert__closures_____invoke__h25554e005bd8517d: (a: number, b: number, c: any) => void;
|
|
15
15
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
16
16
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
17
17
|
export const __wbindgen_exn_store: (a: number) => void;
|
package/runtime/rust-runtime.js
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
let rustModulePromise;
|
|
2
2
|
|
|
3
|
+
function isNodeRuntime() {
|
|
4
|
+
return typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export async function loadRustModule() {
|
|
4
8
|
if (!rustModulePromise) {
|
|
5
9
|
rustModulePromise = (async () => {
|
|
6
10
|
try {
|
|
7
11
|
const moduleValue = await import("../pkg/simple_agents_wasm.js");
|
|
8
12
|
const wasmUrl = new URL("../pkg/simple_agents_wasm_bg.wasm", import.meta.url);
|
|
9
|
-
|
|
13
|
+
let initArg;
|
|
14
|
+
if (isNodeRuntime()) {
|
|
15
|
+
const [{ readFile }, { fileURLToPath }] = await Promise.all([
|
|
16
|
+
import("node:fs/promises"),
|
|
17
|
+
import("node:url")
|
|
18
|
+
]);
|
|
19
|
+
initArg = { module_or_path: await readFile(fileURLToPath(wasmUrl)) };
|
|
20
|
+
} else {
|
|
21
|
+
initArg = { module_or_path: wasmUrl };
|
|
22
|
+
}
|
|
23
|
+
await moduleValue.default(initArg);
|
|
10
24
|
return moduleValue;
|
|
11
25
|
} catch (error) {
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
27
|
+
throw new Error(
|
|
28
|
+
`[simple-agents-wasm] Failed to load Rust WASM backend. ` +
|
|
29
|
+
`Build artifacts are required (run "npm run build" in bindings/wasm/simple-agents-wasm). ` +
|
|
30
|
+
`Original error: ${reason}`
|
|
31
|
+
);
|
|
14
32
|
}
|
|
15
33
|
})();
|
|
16
34
|
}
|
package/rust/Cargo.toml
CHANGED
package/rust/src/lib.rs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
use js_sys::{Array, Function, Object, Promise, Reflect};
|
|
2
2
|
use serde::{Deserialize, Serialize};
|
|
3
3
|
use serde_json::{json, Map as JsonMap, Value as JsonValue};
|
|
4
|
+
use std::cell::RefCell;
|
|
4
5
|
use std::collections::HashMap;
|
|
6
|
+
use wasm_bindgen::closure::Closure;
|
|
5
7
|
use wasm_bindgen::prelude::*;
|
|
6
8
|
use wasm_bindgen::JsCast;
|
|
7
9
|
use wasm_bindgen_futures::JsFuture;
|
|
@@ -86,6 +88,7 @@ struct WorkflowDoc {
|
|
|
86
88
|
|
|
87
89
|
#[derive(Deserialize, Clone)]
|
|
88
90
|
struct GraphWorkflowDoc {
|
|
91
|
+
id: Option<String>,
|
|
89
92
|
model: Option<String>,
|
|
90
93
|
entry_node: String,
|
|
91
94
|
nodes: Vec<GraphWorkflowNode>,
|
|
@@ -125,6 +128,7 @@ struct GraphLlmCall {
|
|
|
125
128
|
temperature: Option<f64>,
|
|
126
129
|
messages_path: Option<String>,
|
|
127
130
|
append_prompt_as_user: Option<bool>,
|
|
131
|
+
stream: Option<bool>,
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
#[derive(Deserialize, Clone)]
|
|
@@ -271,6 +275,29 @@ struct WorkflowRunOptions {
|
|
|
271
275
|
trace: Option<JsonValue>,
|
|
272
276
|
#[serde(skip)]
|
|
273
277
|
functions_js: Option<JsValue>,
|
|
278
|
+
#[serde(skip)]
|
|
279
|
+
on_event_js: Option<Function>,
|
|
280
|
+
/// Injected by JS as `__fetchImpl` so HTTP calls use the same fetch as tests (no global race).
|
|
281
|
+
#[serde(skip)]
|
|
282
|
+
fetch_js: Option<JsValue>,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
struct FetchOverrideGuard<'a> {
|
|
286
|
+
cell: &'a RefCell<Option<JsValue>>,
|
|
287
|
+
previous: Option<JsValue>,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
impl<'a> FetchOverrideGuard<'a> {
|
|
291
|
+
fn new(cell: &'a RefCell<Option<JsValue>>, value: Option<JsValue>) -> Self {
|
|
292
|
+
let previous = cell.replace(value);
|
|
293
|
+
Self { cell, previous }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
impl Drop for FetchOverrideGuard<'_> {
|
|
298
|
+
fn drop(&mut self) {
|
|
299
|
+
self.cell.replace(self.previous.take());
|
|
300
|
+
}
|
|
274
301
|
}
|
|
275
302
|
|
|
276
303
|
fn js_error(message: impl Into<String>) -> JsValue {
|
|
@@ -289,6 +316,24 @@ fn config_error(message: impl Into<String>) -> JsValue {
|
|
|
289
316
|
.into()
|
|
290
317
|
}
|
|
291
318
|
|
|
319
|
+
/// Plain objects for JS `Function.call` / stream callbacks. `serde_wasm_bindgen::to_value`
|
|
320
|
+
/// can yield empty externref objects in Node for callback arguments; `JSON.parse` matches browser behavior.
|
|
321
|
+
fn json_value_to_js_plain(value: &JsonValue) -> Result<JsValue, JsValue> {
|
|
322
|
+
let s =
|
|
323
|
+
serde_json::to_string(value).map_err(|_| js_error("failed to serialize value for JS"))?;
|
|
324
|
+
js_sys::JSON::parse(&s).map_err(|_| js_error("failed to parse JSON for JS"))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
fn emit_workflow_event(on_event: &Option<Function>, event: JsonValue) -> Result<(), JsValue> {
|
|
328
|
+
if let Some(callback) = on_event {
|
|
329
|
+
let event_js = json_value_to_js_plain(&event)?;
|
|
330
|
+
callback
|
|
331
|
+
.call1(&JsValue::NULL, &event_js)
|
|
332
|
+
.map_err(|_| js_error("failed to call workflow stream callback"))?;
|
|
333
|
+
}
|
|
334
|
+
Ok(())
|
|
335
|
+
}
|
|
336
|
+
|
|
292
337
|
fn now_millis() -> f64 {
|
|
293
338
|
let global = js_sys::global();
|
|
294
339
|
let performance = Reflect::get(&global, &JsValue::from_str("performance")).ok();
|
|
@@ -463,13 +508,23 @@ fn call_method2_sync(
|
|
|
463
508
|
}
|
|
464
509
|
|
|
465
510
|
async fn js_fetch(
|
|
511
|
+
fetch_override: Option<JsValue>,
|
|
466
512
|
url: &str,
|
|
467
513
|
body: &JsonValue,
|
|
468
514
|
headers: &HashMap<String, String>,
|
|
469
515
|
) -> Result<JsValue, JsValue> {
|
|
470
516
|
let global = js_sys::global();
|
|
471
|
-
let fetch_value =
|
|
472
|
-
.
|
|
517
|
+
let fetch_value = if let Some(f) = fetch_override {
|
|
518
|
+
if f.is_function() {
|
|
519
|
+
f
|
|
520
|
+
} else {
|
|
521
|
+
Reflect::get(&global, &JsValue::from_str("fetch"))
|
|
522
|
+
.map_err(|_| js_error("global fetch is unavailable"))?
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
Reflect::get(&global, &JsValue::from_str("fetch"))
|
|
526
|
+
.map_err(|_| js_error("global fetch is unavailable"))?
|
|
527
|
+
};
|
|
473
528
|
let fetch_fn = fetch_value
|
|
474
529
|
.dyn_into::<Function>()
|
|
475
530
|
.map_err(|_| js_error("global fetch is not callable"))?;
|
|
@@ -985,6 +1040,7 @@ pub struct WasmClient {
|
|
|
985
1040
|
base_url: String,
|
|
986
1041
|
api_key: String,
|
|
987
1042
|
headers: HashMap<String, String>,
|
|
1043
|
+
fetch_override: RefCell<Option<JsValue>>,
|
|
988
1044
|
}
|
|
989
1045
|
|
|
990
1046
|
#[wasm_bindgen]
|
|
@@ -1013,6 +1069,7 @@ impl WasmClient {
|
|
|
1013
1069
|
base_url: normalize_base_url(&base),
|
|
1014
1070
|
api_key: parsed.api_key,
|
|
1015
1071
|
headers: parsed.headers.unwrap_or_default(),
|
|
1072
|
+
fetch_override: RefCell::new(None),
|
|
1016
1073
|
})
|
|
1017
1074
|
}
|
|
1018
1075
|
|
|
@@ -1066,7 +1123,9 @@ impl WasmClient {
|
|
|
1066
1123
|
.or_insert_with(|| "https://simpleagents.dev".to_string());
|
|
1067
1124
|
}
|
|
1068
1125
|
|
|
1126
|
+
let fetch_override = self.fetch_override.borrow().clone();
|
|
1069
1127
|
let response = js_fetch(
|
|
1128
|
+
fetch_override,
|
|
1070
1129
|
&format!("{}/chat/completions", self.base_url),
|
|
1071
1130
|
&body,
|
|
1072
1131
|
&headers,
|
|
@@ -1235,7 +1294,9 @@ impl WasmClient {
|
|
|
1235
1294
|
.or_insert_with(|| "https://simpleagents.dev".to_string());
|
|
1236
1295
|
}
|
|
1237
1296
|
|
|
1297
|
+
let fetch_override = self.fetch_override.borrow().clone();
|
|
1238
1298
|
let response = js_fetch(
|
|
1299
|
+
fetch_override,
|
|
1239
1300
|
&format!("{}/chat/completions", self.base_url),
|
|
1240
1301
|
&body,
|
|
1241
1302
|
&headers,
|
|
@@ -1260,8 +1321,7 @@ impl WasmClient {
|
|
|
1260
1321
|
"eventType": "error",
|
|
1261
1322
|
"error": { "message": message }
|
|
1262
1323
|
});
|
|
1263
|
-
let event_js =
|
|
1264
|
-
.map_err(|_| js_error("failed to serialize stream error event"))?;
|
|
1324
|
+
let event_js = json_value_to_js_plain(&err_event)?;
|
|
1265
1325
|
on_event
|
|
1266
1326
|
.call1(&JsValue::NULL, &event_js)
|
|
1267
1327
|
.map_err(|_| js_error("failed to call stream callback"))?;
|
|
@@ -1337,8 +1397,7 @@ impl WasmClient {
|
|
|
1337
1397
|
"raw": data,
|
|
1338
1398
|
}
|
|
1339
1399
|
});
|
|
1340
|
-
let event_js =
|
|
1341
|
-
.map_err(|_| js_error("failed to serialize stream delta event"))?;
|
|
1400
|
+
let event_js = json_value_to_js_plain(&delta_event)?;
|
|
1342
1401
|
on_event
|
|
1343
1402
|
.call1(&JsValue::NULL, &event_js)
|
|
1344
1403
|
.map_err(|_| js_error("failed to call stream callback"))?;
|
|
@@ -1348,8 +1407,7 @@ impl WasmClient {
|
|
|
1348
1407
|
.await?;
|
|
1349
1408
|
|
|
1350
1409
|
let done_event = json!({ "eventType": "done" });
|
|
1351
|
-
let done_js =
|
|
1352
|
-
.map_err(|_| js_error("failed to serialize stream done event"))?;
|
|
1410
|
+
let done_js = json_value_to_js_plain(&done_event)?;
|
|
1353
1411
|
on_event
|
|
1354
1412
|
.call1(&JsValue::NULL, &done_js)
|
|
1355
1413
|
.map_err(|_| js_error("failed to call stream callback"))?;
|
|
@@ -1396,14 +1454,34 @@ impl WasmClient {
|
|
|
1396
1454
|
telemetry: None,
|
|
1397
1455
|
trace: None,
|
|
1398
1456
|
functions_js: None,
|
|
1457
|
+
on_event_js: None,
|
|
1458
|
+
fetch_js: None,
|
|
1399
1459
|
};
|
|
1400
1460
|
if let Some(options_js) = workflow_options {
|
|
1401
1461
|
options = serde_wasm_bindgen::from_value(options_js.clone())
|
|
1402
1462
|
.map_err(|_| config_error("invalid workflowOptions object"))?;
|
|
1403
1463
|
let functions_value = Reflect::get(&options_js, &JsValue::from_str("functions")).ok();
|
|
1404
|
-
options.functions_js = functions_value
|
|
1464
|
+
options.functions_js = functions_value.and_then(|v| {
|
|
1465
|
+
if v.is_undefined() || v.is_null() {
|
|
1466
|
+
None
|
|
1467
|
+
} else {
|
|
1468
|
+
Some(v)
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
let on_event_value = Reflect::get(&options_js, &JsValue::from_str("onEvent")).ok();
|
|
1472
|
+
options.on_event_js = on_event_value.and_then(|v| {
|
|
1473
|
+
if v.is_undefined() || v.is_null() {
|
|
1474
|
+
None
|
|
1475
|
+
} else {
|
|
1476
|
+
v.dyn_into::<Function>().ok()
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
let fetch_js = Reflect::get(&options_js, &JsValue::from_str("__fetchImpl")).ok();
|
|
1480
|
+
options.fetch_js = fetch_js;
|
|
1405
1481
|
}
|
|
1406
1482
|
|
|
1483
|
+
let _fetch_guard = FetchOverrideGuard::new(&self.fetch_override, options.fetch_js.clone());
|
|
1484
|
+
|
|
1407
1485
|
if raw_doc.get("entry_node").is_some() && raw_doc.get("nodes").is_some() {
|
|
1408
1486
|
let graph_doc: GraphWorkflowDoc = serde_json::from_value(raw_doc.clone())
|
|
1409
1487
|
.map_err(|error| config_error(format!("invalid graph workflow YAML: {error}")))?;
|
|
@@ -1445,6 +1523,14 @@ impl WasmClient {
|
|
|
1445
1523
|
let total_reasoning_tokens: u64 = 0;
|
|
1446
1524
|
let mut llm_nodes_without_usage: Vec<String> = Vec::new();
|
|
1447
1525
|
|
|
1526
|
+
emit_workflow_event(
|
|
1527
|
+
&options.on_event_js,
|
|
1528
|
+
json!({
|
|
1529
|
+
"event_type": "workflow_started",
|
|
1530
|
+
"workflow_id": graph_doc.id.clone().unwrap_or_else(|| "wasm_workflow".to_string())
|
|
1531
|
+
}),
|
|
1532
|
+
)?;
|
|
1533
|
+
|
|
1448
1534
|
while !pointer.is_empty() {
|
|
1449
1535
|
iterations += 1;
|
|
1450
1536
|
if iterations > 1000 {
|
|
@@ -1519,24 +1605,74 @@ impl WasmClient {
|
|
|
1519
1605
|
tool_calls: None,
|
|
1520
1606
|
});
|
|
1521
1607
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1608
|
+
json_value_to_js_plain(
|
|
1609
|
+
&serde_json::to_value(&history)
|
|
1610
|
+
.map_err(|_| js_error("failed to serialize graph llm messages"))?,
|
|
1611
|
+
)?
|
|
1524
1612
|
} else {
|
|
1525
1613
|
JsValue::from_str(&prompt)
|
|
1526
1614
|
};
|
|
1527
1615
|
|
|
1528
1616
|
let opts = json!({ "temperature": llm.temperature });
|
|
1529
|
-
let completion_js =
|
|
1530
|
-
.
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1617
|
+
let completion_js = if llm.stream.unwrap_or(false) && options.on_event_js.is_some() {
|
|
1618
|
+
let node_id = node.id.clone();
|
|
1619
|
+
let step_id = node.id.clone();
|
|
1620
|
+
let workflow_on_event = options
|
|
1621
|
+
.on_event_js
|
|
1622
|
+
.clone()
|
|
1623
|
+
.ok_or_else(|| js_error("missing workflow stream callback"))?;
|
|
1624
|
+
let mapped_stream_cb = Closure::wrap(Box::new(move |stream_event_js: JsValue| {
|
|
1625
|
+
let stream_event: JsonValue =
|
|
1626
|
+
serde_wasm_bindgen::from_value(stream_event_js).unwrap_or(JsonValue::Null);
|
|
1627
|
+
let stream_event_type = stream_event
|
|
1628
|
+
.get("eventType")
|
|
1629
|
+
.and_then(JsonValue::as_str)
|
|
1630
|
+
.unwrap_or_default();
|
|
1631
|
+
|
|
1632
|
+
if stream_event_type == "delta" {
|
|
1633
|
+
if let Some(delta) = stream_event
|
|
1634
|
+
.get("delta")
|
|
1635
|
+
.and_then(|v| v.get("content"))
|
|
1636
|
+
.and_then(JsonValue::as_str)
|
|
1637
|
+
{
|
|
1638
|
+
let workflow_event = json!({
|
|
1639
|
+
"event_type": "node_stream_delta",
|
|
1640
|
+
"node_id": node_id.clone(),
|
|
1641
|
+
"step_id": step_id.clone(),
|
|
1642
|
+
"delta": delta
|
|
1643
|
+
});
|
|
1644
|
+
if let Ok(event_js) = json_value_to_js_plain(&workflow_event) {
|
|
1645
|
+
let _ = workflow_on_event.call1(&JsValue::NULL, &event_js);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
} else if stream_event_type == "done" {
|
|
1649
|
+
let workflow_event = json!({
|
|
1650
|
+
"event_type": "node_stream_snapshot",
|
|
1651
|
+
"node_id": node_id.clone(),
|
|
1652
|
+
"step_id": step_id.clone(),
|
|
1653
|
+
"metadata": { "is_complete": true }
|
|
1654
|
+
});
|
|
1655
|
+
if let Ok(event_js) = json_value_to_js_plain(&workflow_event) {
|
|
1656
|
+
let _ = workflow_on_event.call1(&JsValue::NULL, &event_js);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}) as Box<dyn FnMut(JsValue)>);
|
|
1660
|
+
let stream_callback_fn: Function =
|
|
1661
|
+
mapped_stream_cb.as_ref().unchecked_ref::<Function>().clone();
|
|
1662
|
+
let result = self
|
|
1663
|
+
.stream_events(
|
|
1664
|
+
model,
|
|
1665
|
+
prompt_js,
|
|
1666
|
+
stream_callback_fn,
|
|
1667
|
+
Some(json_value_to_js_plain(&opts)?),
|
|
1668
|
+
)
|
|
1669
|
+
.await;
|
|
1670
|
+
drop(mapped_stream_cb);
|
|
1671
|
+
result?
|
|
1672
|
+
} else {
|
|
1673
|
+
self.complete(model, prompt_js, Some(json_value_to_js_plain(&opts)?))
|
|
1674
|
+
.await?
|
|
1675
|
+
};
|
|
1540
1676
|
let completion: JsonValue = serde_wasm_bindgen::from_value(completion_js)
|
|
1541
1677
|
.map_err(|_| js_error("failed to parse completion result"))?;
|
|
1542
1678
|
|
|
@@ -1630,17 +1766,23 @@ impl WasmClient {
|
|
|
1630
1766
|
.unwrap_or_else(|| handler.clone());
|
|
1631
1767
|
let functions_js = options.functions_js.clone().ok_or_else(|| {
|
|
1632
1768
|
config_error(format!(
|
|
1633
|
-
"custom_worker node '{}' requires workflowOptions.functions",
|
|
1634
|
-
node.id
|
|
1769
|
+
"custom_worker node '{}' requires workflowOptions.functions['{}']",
|
|
1770
|
+
node.id, lookup_key
|
|
1635
1771
|
))
|
|
1636
1772
|
})?;
|
|
1637
1773
|
let function_value = Reflect::get(&functions_js, &JsValue::from_str(&lookup_key))
|
|
1638
1774
|
.map_err(|_| {
|
|
1639
1775
|
config_error(format!(
|
|
1640
|
-
"
|
|
1641
|
-
lookup_key
|
|
1776
|
+
"custom_worker node '{}' requires workflowOptions.functions['{}']",
|
|
1777
|
+
node.id, lookup_key
|
|
1642
1778
|
))
|
|
1643
1779
|
})?;
|
|
1780
|
+
if function_value.is_undefined() || function_value.is_null() {
|
|
1781
|
+
return Err(config_error(format!(
|
|
1782
|
+
"custom_worker node '{}' requires workflowOptions.functions['{}']",
|
|
1783
|
+
node.id, lookup_key
|
|
1784
|
+
)));
|
|
1785
|
+
}
|
|
1644
1786
|
let function = function_value.dyn_into::<Function>().map_err(|_| {
|
|
1645
1787
|
config_error(format!(
|
|
1646
1788
|
"custom_worker node '{}' requires workflowOptions.functions['{}']",
|
|
@@ -1660,10 +1802,8 @@ impl WasmClient {
|
|
|
1660
1802
|
.unwrap_or(JsonValue::Null),
|
|
1661
1803
|
"nodeId": node.id.clone()
|
|
1662
1804
|
});
|
|
1663
|
-
let args_js =
|
|
1664
|
-
|
|
1665
|
-
let context_js = serde_wasm_bindgen::to_value(&graph_context)
|
|
1666
|
-
.map_err(|_| js_error("failed to serialize graph context"))?;
|
|
1805
|
+
let args_js = json_value_to_js_plain(&worker_args)?;
|
|
1806
|
+
let context_js = json_value_to_js_plain(&graph_context)?;
|
|
1667
1807
|
let worker_call_output = function
|
|
1668
1808
|
.call2(&JsValue::NULL, &args_js, &context_js)
|
|
1669
1809
|
.map_err(|_| {
|
|
@@ -1741,6 +1881,17 @@ impl WasmClient {
|
|
|
1741
1881
|
trace.push(node.id.clone());
|
|
1742
1882
|
}
|
|
1743
1883
|
|
|
1884
|
+
emit_workflow_event(
|
|
1885
|
+
&options.on_event_js,
|
|
1886
|
+
json!({
|
|
1887
|
+
"event_type": "workflow_completed",
|
|
1888
|
+
"workflow_id": raw_doc
|
|
1889
|
+
.get("id")
|
|
1890
|
+
.and_then(JsonValue::as_str)
|
|
1891
|
+
.unwrap_or("wasm_workflow")
|
|
1892
|
+
}),
|
|
1893
|
+
)?;
|
|
1894
|
+
|
|
1744
1895
|
let total_elapsed_ms = (now_millis() - workflow_started).max(0.0) as u64;
|
|
1745
1896
|
let terminal_node = trace.last().cloned().unwrap_or_default();
|
|
1746
1897
|
let workflow_id = raw_doc
|
|
@@ -1814,8 +1965,9 @@ impl WasmClient {
|
|
|
1814
1965
|
trace_id: None,
|
|
1815
1966
|
metadata: Some(json!({"nerdstats": nerdstats})),
|
|
1816
1967
|
};
|
|
1817
|
-
|
|
1818
|
-
.map_err(|_| js_error("failed to serialize workflow result"))
|
|
1968
|
+
let jv = serde_json::to_value(&result)
|
|
1969
|
+
.map_err(|_| js_error("failed to serialize workflow result"))?;
|
|
1970
|
+
return json_value_to_js_plain(&jv);
|
|
1819
1971
|
}
|
|
1820
1972
|
|
|
1821
1973
|
let doc: WorkflowDoc = serde_json::from_value(raw_doc)
|
|
@@ -1885,11 +2037,7 @@ impl WasmClient {
|
|
|
1885
2037
|
.complete(
|
|
1886
2038
|
model,
|
|
1887
2039
|
JsValue::from_str(&prompt),
|
|
1888
|
-
Some(
|
|
1889
|
-
serde_wasm_bindgen::to_value(&opts).map_err(|_| {
|
|
1890
|
-
js_error("failed to serialize completion options")
|
|
1891
|
-
})?,
|
|
1892
|
-
),
|
|
2040
|
+
Some(json_value_to_js_plain(&opts)?),
|
|
1893
2041
|
)
|
|
1894
2042
|
.await?;
|
|
1895
2043
|
let completion: JsonValue = serde_wasm_bindgen::from_value(completion_js)
|
|
@@ -1956,11 +2104,8 @@ impl WasmClient {
|
|
|
1956
2104
|
.unwrap_or_else(|| JsonValue::Object(JsonMap::new())),
|
|
1957
2105
|
&context,
|
|
1958
2106
|
);
|
|
1959
|
-
let args_js =
|
|
1960
|
-
|
|
1961
|
-
let context_js =
|
|
1962
|
-
serde_wasm_bindgen::to_value(&JsonValue::Object(context.clone()))
|
|
1963
|
-
.map_err(|_| js_error("failed to serialize workflow context"))?;
|
|
2107
|
+
let args_js = json_value_to_js_plain(&args_value)?;
|
|
2108
|
+
let context_js = json_value_to_js_plain(&JsonValue::Object(context.clone()))?;
|
|
1964
2109
|
let call_output = function
|
|
1965
2110
|
.call2(&JsValue::NULL, &args_js, &context_js)
|
|
1966
2111
|
.map_err(|_| {
|
|
@@ -2017,18 +2162,32 @@ impl WasmClient {
|
|
|
2017
2162
|
pointer += 1;
|
|
2018
2163
|
}
|
|
2019
2164
|
|
|
2165
|
+
let trace: Vec<String> = events
|
|
2166
|
+
.iter()
|
|
2167
|
+
.filter(|e| e.status == "completed")
|
|
2168
|
+
.map(|e| e.step_id.clone())
|
|
2169
|
+
.collect();
|
|
2170
|
+
let terminal_node = trace.last().cloned();
|
|
2171
|
+
|
|
2172
|
+
let context_value = JsonValue::Object(context);
|
|
2173
|
+
let outputs_value = context_value
|
|
2174
|
+
.as_object()
|
|
2175
|
+
.cloned()
|
|
2176
|
+
.map(JsonValue::Object)
|
|
2177
|
+
.unwrap_or_else(|| JsonValue::Object(JsonMap::new()));
|
|
2178
|
+
|
|
2020
2179
|
let result = WorkflowRunResult {
|
|
2021
2180
|
status: "ok".to_string(),
|
|
2022
|
-
context:
|
|
2023
|
-
output,
|
|
2181
|
+
context: context_value.clone(),
|
|
2182
|
+
output: output.clone(),
|
|
2024
2183
|
events,
|
|
2025
|
-
workflow_id:
|
|
2184
|
+
workflow_id: Some("wasm_workflow".to_string()),
|
|
2026
2185
|
entry_node: None,
|
|
2027
2186
|
email_text: None,
|
|
2028
|
-
trace:
|
|
2029
|
-
outputs:
|
|
2030
|
-
terminal_node
|
|
2031
|
-
terminal_output:
|
|
2187
|
+
trace: Some(trace),
|
|
2188
|
+
outputs: Some(outputs_value),
|
|
2189
|
+
terminal_node,
|
|
2190
|
+
terminal_output: output,
|
|
2032
2191
|
step_timings: None,
|
|
2033
2192
|
total_elapsed_ms: None,
|
|
2034
2193
|
ttft_ms: None,
|
|
@@ -2040,8 +2199,9 @@ impl WasmClient {
|
|
|
2040
2199
|
trace_id: None,
|
|
2041
2200
|
metadata: None,
|
|
2042
2201
|
};
|
|
2043
|
-
|
|
2044
|
-
.map_err(|_| js_error("failed to serialize workflow result"))
|
|
2202
|
+
let jv = serde_json::to_value(&result)
|
|
2203
|
+
.map_err(|_| js_error("failed to serialize workflow result"))?;
|
|
2204
|
+
json_value_to_js_plain(&jv)
|
|
2045
2205
|
}
|
|
2046
2206
|
|
|
2047
2207
|
#[wasm_bindgen(js_name = runWorkflowYaml)]
|
|
@@ -2080,8 +2240,7 @@ impl WasmClient {
|
|
|
2080
2240
|
serde_json::json!([])
|
|
2081
2241
|
};
|
|
2082
2242
|
|
|
2083
|
-
let input_js =
|
|
2084
|
-
.map_err(|_| js_error("failed to serialize messages input"))?;
|
|
2243
|
+
let input_js = json_value_to_js_plain(&serde_json::json!({ "messages": messages_value }))?;
|
|
2085
2244
|
|
|
2086
2245
|
self.run_workflow_yaml_string(yaml_text, input_js, options).await
|
|
2087
2246
|
}
|
|
@@ -2091,10 +2250,3 @@ impl WasmClient {
|
|
|
2091
2250
|
pub fn supports_rust_wasm() -> bool {
|
|
2092
2251
|
true
|
|
2093
2252
|
}
|
|
2094
|
-
|
|
2095
|
-
#[wasm_bindgen(js_name = toJsArray)]
|
|
2096
|
-
pub fn to_js_array(value: JsValue) -> Array {
|
|
2097
|
-
let arr = Array::new();
|
|
2098
|
-
arr.push(&value);
|
|
2099
|
-
arr
|
|
2100
|
-
}
|