Skip to main content

sage_runtime_web/
lib.rs

1//! WASM platform layer for the Sage runtime.
2//!
3//! Provides browser-compatible implementations of platform primitives:
4//! - `spawn_local` for async task spawning (no `Send` required)
5//! - `sleep` via browser `setTimeout`
6//! - Console logging via `web-sys`
7//! - `web-time` re-export for `Instant` shim
8//! - Thread-local LLM config injection for browser environments
9
10#![forbid(unsafe_code)]
11
12use std::cell::RefCell;
13use std::time::Duration;
14use wasm_bindgen::prelude::*;
15use wasm_bindgen_futures::JsFuture;
16
17/// Re-export `web_time::Instant` as the WASM-compatible `Instant`.
18pub use web_time::Instant;
19
20/// Re-export `wasm_bindgen_futures::spawn_local`.
21pub use wasm_bindgen_futures::spawn_local;
22
23/// Re-export panic hook setup.
24pub use console_error_panic_hook::set_once as set_panic_hook;
25
26/// LLM configuration for WASM environments.
27///
28/// Since `std::env::var` is not available in the browser, LLM config
29/// is injected via `sage_configure()` from JavaScript.
30#[derive(Debug, Clone)]
31pub struct WasmLlmConfig {
32    pub api_key: String,
33    pub base_url: String,
34    pub model: String,
35}
36
37impl Default for WasmLlmConfig {
38    fn default() -> Self {
39        Self {
40            api_key: String::new(),
41            base_url: "https://api.openai.com/v1".to_string(),
42            model: "gpt-4o-mini".to_string(),
43        }
44    }
45}
46
47thread_local! {
48    static WASM_LLM_CONFIG: RefCell<WasmLlmConfig> = RefCell::new(WasmLlmConfig::default());
49}
50
51/// Configure the LLM endpoint from JavaScript.
52///
53/// Call this before starting the Sage agent:
54/// ```js
55/// import init, { sage_configure } from './pkg/agent.js';
56/// await init();
57/// sage_configure('https://my-proxy.example.com/v1', 'gpt-4o', '');
58/// ```
59#[wasm_bindgen]
60pub fn sage_configure(base_url: &str, model: &str, api_key: &str) {
61    WASM_LLM_CONFIG.with(|c| {
62        *c.borrow_mut() = WasmLlmConfig {
63            api_key: api_key.to_string(),
64            base_url: base_url.to_string(),
65            model: model.to_string(),
66        };
67    });
68}
69
70/// Get the current WASM LLM configuration.
71pub fn get_llm_config() -> WasmLlmConfig {
72    WASM_LLM_CONFIG.with(|c| c.borrow().clone())
73}
74
75/// Async sleep using browser `setTimeout`.
76pub async fn sleep(duration: Duration) {
77    let ms = duration.as_millis() as i32;
78    let promise = js_sys::Promise::new(&mut |resolve, _| {
79        // Use global scope setTimeout (works in both Window and Worker contexts)
80        let global = js_sys::global();
81        let _ = js_sys::Reflect::apply(
82            &js_sys::Function::from(js_sys::Reflect::get(&global, &"setTimeout".into()).unwrap()),
83            &global,
84            &js_sys::Array::of2(&resolve, &JsValue::from(ms)),
85        );
86    });
87    let _ = JsFuture::from(promise).await;
88}
89
90/// Log a message to the browser console.
91pub fn console_log(msg: &str) {
92    web_sys::console::log_1(&msg.into());
93}
94
95/// Log a warning to the browser console.
96pub fn console_warn(msg: &str) {
97    web_sys::console::warn_1(&msg.into());
98}
99
100/// Log an error to the browser console.
101pub fn console_error(msg: &str) {
102    web_sys::console::error_1(&msg.into());
103}