use wasm_bindgen::prelude::*;
use crate::loop_runner::NativeStep;
use crate::types::{AgentConfig, ReActStep, ToolResult};
use crate::ToolSpec;
fn js_err(e: crate::AgentError) -> JsError {
JsError::new(&e.to_string())
}
fn to_js(v: &serde_json::Value) -> Result<JsValue, JsError> {
js_sys::JSON::parse(&v.to_string()).map_err(|_| JsError::new("could not convert value to JavaScript"))
}
#[wasm_bindgen(js_name = Agent)]
pub struct JsAgent {
inner: crate::Agent,
}
#[wasm_bindgen(js_class = Agent)]
impl JsAgent {
#[wasm_bindgen(constructor)]
pub fn new(max_iterations: Option<u32>, context_tokens: Option<usize>, recover_from_errors: Option<bool>) -> JsAgent {
let d = AgentConfig::default();
let config = AgentConfig {
max_iterations: max_iterations.unwrap_or(d.max_iterations),
context_token_limit: context_tokens.unwrap_or(d.context_token_limit),
recover_from_errors: recover_from_errors.unwrap_or(false),
..d
};
JsAgent { inner: crate::Agent::new(config) }
}
#[wasm_bindgen(js_name = registerTool)]
pub fn register_tool(&mut self, name: &str, description: &str, schema: &str, f: js_sys::Function) -> Result<(), JsError> {
let tool = name.to_string();
self.inner
.tools_mut()
.register(
ToolSpec::new(name, description, schema),
Box::new(move |input: &str| match f.call1(&JsValue::NULL, &JsValue::from_str(input)) {
Ok(v) => {
let output = v.as_string().unwrap_or_else(|| {
js_sys::JSON::stringify(&v).ok().and_then(|s| s.as_string()).unwrap_or_default()
});
ToolResult { tool_name: tool.clone(), output, success: true }
}
Err(e) => {
let msg = js_sys::Reflect::get(&e, &JsValue::from_str("message"))
.ok()
.and_then(|m| m.as_string())
.or_else(|| e.as_string())
.unwrap_or_else(|| "tool threw an exception".into());
ToolResult { tool_name: tool.clone(), output: msg, success: false }
}
}),
)
.map_err(js_err)
}
#[wasm_bindgen(js_name = addSystemPrompt)]
pub fn add_system_prompt(&mut self) {
self.inner.add_system_prompt();
}
pub fn ask(&mut self, question: &str) {
self.inner.ask(question);
}
pub fn step(&mut self, reply: &str) -> Result<JsValue, JsError> {
let step = self.inner.step(reply).map_err(js_err)?;
let v = match step {
ReActStep::Thought(t) => serde_json::json!({"kind": "thought", "content": t}),
ReActStep::Action { tool, input } => serde_json::json!({"kind": "action", "content": input, "tool": tool, "input": input}),
ReActStep::Observation(o) => serde_json::json!({"kind": "observation", "content": o}),
ReActStep::FinalAnswer(a) => serde_json::json!({"kind": "final", "content": a}),
};
to_js(&v)
}
#[wasm_bindgen(js_name = stepResponse)]
pub fn step_response(&mut self, body: &str) -> Result<JsValue, JsError> {
let v = match self.inner.step_response(body).map_err(js_err)? {
NativeStep::ToolCalls(results) => serde_json::json!({"kind": "tool_calls", "results": results}),
NativeStep::Final(text) => serde_json::json!({"kind": "final", "text": text}),
};
to_js(&v)
}
#[wasm_bindgen(js_name = openaiTools)]
pub fn openai_tools(&self) -> Result<JsValue, JsError> {
to_js(&serde_json::Value::Array(self.inner.tools().openai_tools().map_err(js_err)?))
}
#[wasm_bindgen(js_name = anthropicTools)]
pub fn anthropic_tools(&self) -> Result<JsValue, JsError> {
to_js(&serde_json::Value::Array(self.inner.tools().anthropic_tools().map_err(js_err)?))
}
pub fn transcript(&self) -> String {
self.inner.transcript()
}
#[wasm_bindgen(js_name = chatMessages)]
pub fn chat_messages(&self) -> Result<JsValue, JsError> {
to_js(&serde_json::Value::Array(self.inner.history().to_chat_messages()))
}
#[wasm_bindgen(getter)]
pub fn steps(&self) -> usize {
self.inner.iteration_count()
}
}