1use wasm_bindgen::prelude::*;
21
22use crate::loop_runner::NativeStep;
23use crate::types::{AgentConfig, ReActStep, ToolResult};
24use crate::ToolSpec;
25
26fn js_err(e: crate::AgentError) -> JsError {
27 JsError::new(&e.to_string())
28}
29
30fn to_js(v: &serde_json::Value) -> Result<JsValue, JsError> {
31 js_sys::JSON::parse(&v.to_string()).map_err(|_| JsError::new("could not convert value to JavaScript"))
32}
33
34#[wasm_bindgen(js_name = Agent)]
36pub struct JsAgent {
37 inner: crate::Agent,
38}
39
40#[wasm_bindgen(js_class = Agent)]
41impl JsAgent {
42 #[wasm_bindgen(constructor)]
44 pub fn new(max_iterations: Option<u32>, context_tokens: Option<usize>, recover_from_errors: Option<bool>) -> JsAgent {
45 let d = AgentConfig::default();
46 let config = AgentConfig {
47 max_iterations: max_iterations.unwrap_or(d.max_iterations),
48 context_token_limit: context_tokens.unwrap_or(d.context_token_limit),
49 recover_from_errors: recover_from_errors.unwrap_or(false),
50 ..d
51 };
52 JsAgent { inner: crate::Agent::new(config) }
53 }
54
55 #[wasm_bindgen(js_name = registerTool)]
57 pub fn register_tool(&mut self, name: &str, description: &str, schema: &str, f: js_sys::Function) -> Result<(), JsError> {
58 let tool = name.to_string();
59 self.inner
60 .tools_mut()
61 .register(
62 ToolSpec::new(name, description, schema),
63 Box::new(move |input: &str| match f.call1(&JsValue::NULL, &JsValue::from_str(input)) {
64 Ok(v) => {
65 let output = v.as_string().unwrap_or_else(|| {
66 js_sys::JSON::stringify(&v).ok().and_then(|s| s.as_string()).unwrap_or_default()
67 });
68 ToolResult { tool_name: tool.clone(), output, success: true }
69 }
70 Err(e) => {
71 let msg = js_sys::Reflect::get(&e, &JsValue::from_str("message"))
72 .ok()
73 .and_then(|m| m.as_string())
74 .or_else(|| e.as_string())
75 .unwrap_or_else(|| "tool threw an exception".into());
76 ToolResult { tool_name: tool.clone(), output: msg, success: false }
77 }
78 }),
79 )
80 .map_err(js_err)
81 }
82
83 #[wasm_bindgen(js_name = addSystemPrompt)]
85 pub fn add_system_prompt(&mut self) {
86 self.inner.add_system_prompt();
87 }
88
89 pub fn ask(&mut self, question: &str) {
91 self.inner.ask(question);
92 }
93
94 pub fn step(&mut self, reply: &str) -> Result<JsValue, JsError> {
97 let step = self.inner.step(reply).map_err(js_err)?;
98 let v = match step {
99 ReActStep::Thought(t) => serde_json::json!({"kind": "thought", "content": t}),
100 ReActStep::Action { tool, input } => serde_json::json!({"kind": "action", "content": input, "tool": tool, "input": input}),
101 ReActStep::Observation(o) => serde_json::json!({"kind": "observation", "content": o}),
102 ReActStep::FinalAnswer(a) => serde_json::json!({"kind": "final", "content": a}),
103 };
104 to_js(&v)
105 }
106
107 #[wasm_bindgen(js_name = stepResponse)]
111 pub fn step_response(&mut self, body: &str) -> Result<JsValue, JsError> {
112 let v = match self.inner.step_response(body).map_err(js_err)? {
113 NativeStep::ToolCalls(results) => serde_json::json!({"kind": "tool_calls", "results": results}),
114 NativeStep::Final(text) => serde_json::json!({"kind": "final", "text": text}),
115 };
116 to_js(&v)
117 }
118
119 #[wasm_bindgen(js_name = openaiTools)]
121 pub fn openai_tools(&self) -> Result<JsValue, JsError> {
122 to_js(&serde_json::Value::Array(self.inner.tools().openai_tools().map_err(js_err)?))
123 }
124
125 #[wasm_bindgen(js_name = anthropicTools)]
127 pub fn anthropic_tools(&self) -> Result<JsValue, JsError> {
128 to_js(&serde_json::Value::Array(self.inner.tools().anthropic_tools().map_err(js_err)?))
129 }
130
131 pub fn transcript(&self) -> String {
133 self.inner.transcript()
134 }
135
136 #[wasm_bindgen(js_name = chatMessages)]
138 pub fn chat_messages(&self) -> Result<JsValue, JsError> {
139 to_js(&serde_json::Value::Array(self.inner.history().to_chat_messages()))
140 }
141
142 #[wasm_bindgen(getter)]
144 pub fn steps(&self) -> usize {
145 self.inner.iteration_count()
146 }
147}