1use crate::util::{
2 expr_to_value, installed_codecs, keyword, literal_expr, parse_capabilities_expr,
3 shape_from_expr, shape_id, string_literal, symbol_from_eval, symbol_from_value, symbol_of,
4};
5use crate::{AGENT_LIB_ID, Component, ComponentKind, TOOL_EXPORT_KIND};
6use sim_kernel::{
7 Args, CORE_FUNCTION_CLASS_ID, Callable, CapabilityName, ClassRef, Cx, Error, EvalReply,
8 ExportKind, ExportRecord, ExportState, Expr, Object, Result, ShapeRef, Symbol, Value,
9};
10use sim_lib_server::{
11 EvalSite, FrameKind, ServerAddress, ServerFrame, eval_request_from_frame,
12 server_frame_from_reply,
13};
14use sim_shape::{ShapeReport, check_value_report};
15use std::{any::Any, sync::Arc};
16
17#[derive(Clone)]
18pub struct Tool {
21 pub symbol: Symbol,
23 pub description: String,
25 pub args_shape: ShapeRef,
27 pub result_shape: Option<ShapeRef>,
29 pub category: Symbol,
31 pub capabilities: Vec<CapabilityName>,
33 pub function: Value,
35 pub(crate) address: ServerAddress,
36 pub(crate) codecs: Vec<Symbol>,
37}
38
39impl Tool {
40 fn args_match(&self, cx: &mut Cx, args: &[Value]) -> Result<ShapeReport> {
41 let args_value = cx.factory().list(args.to_vec())?;
42 check_value_report(cx, &self.args_shape, args_value)
43 }
44
45 fn check_result_shape(&self, cx: &mut Cx, value: Value) -> Result<Value> {
46 let Some(result_shape) = &self.result_shape else {
47 return Ok(value);
48 };
49 let matched = check_value_report(cx, result_shape, value.clone())?;
50 if matched.accepted {
51 Ok(value)
52 } else {
53 Err(Error::WrongShape {
54 expected: shape_id(result_shape),
55 diagnostics: matched.diagnostics,
56 })
57 }
58 }
59
60 pub(crate) fn call_values(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
61 for capability in &self.capabilities {
62 cx.require(capability)?;
63 }
64 let matched = self.args_match(cx, &args)?;
65 if !matched.accepted {
66 return Err(Error::WrongShape {
67 expected: shape_id(&self.args_shape),
68 diagnostics: matched.diagnostics,
69 });
70 }
71 let value = cx.call_value(self.function.clone(), Args::new(args))?;
72 self.check_result_shape(cx, value)
73 }
74
75 pub(crate) fn metadata_entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
76 let capabilities = cx.factory().list(
77 self.capabilities
78 .iter()
79 .map(|capability| cx.factory().string(capability.as_str().to_owned()))
80 .collect::<Result<Vec<_>>>()?,
81 )?;
82 let result_shape = match &self.result_shape {
83 Some(shape) => shape.clone(),
84 None => cx.factory().nil()?,
85 };
86 Ok(vec![
87 (
88 Symbol::new("kind"),
89 cx.factory().symbol(tool_export_kind_symbol())?,
90 ),
91 (
92 Symbol::new("name"),
93 cx.factory().symbol(self.symbol.clone())?,
94 ),
95 (
96 Symbol::new("description"),
97 cx.factory().string(self.description.clone())?,
98 ),
99 (Symbol::new("args"), self.args_shape.clone()),
100 (Symbol::new("result"), result_shape),
101 (
102 Symbol::new("category"),
103 cx.factory().symbol(self.category.clone())?,
104 ),
105 (Symbol::new("capabilities"), capabilities),
106 ])
107 }
108
109 pub(crate) fn model_descriptor_expr(&self, cx: &mut Cx) -> Result<Expr> {
110 let args = self.args_shape.object().as_expr(cx)?;
111 let result = self
112 .result_shape
113 .as_ref()
114 .map(|shape| shape.object().as_expr(cx))
115 .transpose()?
116 .unwrap_or(Expr::Nil);
117 let capabilities = self
118 .capabilities
119 .iter()
120 .map(|capability| Expr::String(capability.as_str().to_owned()))
121 .collect();
122 Ok(Expr::Map(vec![
123 (
124 Expr::Symbol(Symbol::new("name")),
125 Expr::Symbol(self.symbol.clone()),
126 ),
127 (
128 Expr::Symbol(Symbol::new("description")),
129 Expr::String(self.description.clone()),
130 ),
131 (Expr::Symbol(Symbol::new("args")), args),
132 (Expr::Symbol(Symbol::new("result")), result),
133 (
134 Expr::Symbol(Symbol::new("category")),
135 Expr::Symbol(self.category.clone()),
136 ),
137 (
138 Expr::Symbol(Symbol::new("capabilities")),
139 Expr::List(capabilities),
140 ),
141 ]))
142 }
143
144 fn metadata_value(&self, cx: &mut Cx) -> Result<Value> {
145 let entries = self.metadata_entries(cx)?;
146 cx.factory().table(entries)
147 }
148}
149
150impl Object for Tool {
151 fn display(&self, _cx: &mut Cx) -> Result<String> {
152 Ok(format!("#<tool {}>", self.symbol))
153 }
154
155 fn as_any(&self) -> &dyn Any {
156 self
157 }
158}
159
160impl sim_kernel::ObjectCompat for Tool {
161 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
162 if let Some(value) = cx
163 .registry()
164 .class_by_symbol(&Symbol::qualified("core", "Function"))
165 {
166 return Ok(value.clone());
167 }
168 cx.factory().class_stub(
169 CORE_FUNCTION_CLASS_ID,
170 Symbol::qualified("core", "Function"),
171 )
172 }
173 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
174 self.as_table(cx)?.object().as_expr(cx)
175 }
176 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
177 self.metadata_value(cx)
178 }
179 fn as_callable(&self) -> Option<&dyn Callable> {
180 Some(self)
181 }
182}
183
184impl Callable for Tool {
185 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
186 self.call_values(cx, args.into_vec())
187 }
188}
189
190impl EvalSite for Tool {
191 fn site_kind(&self) -> &'static str {
192 TOOL_EXPORT_KIND
193 }
194
195 fn address(&self) -> &ServerAddress {
196 &self.address
197 }
198
199 fn codecs(&self) -> &[Symbol] {
200 &self.codecs
201 }
202
203 fn answer(&self, cx: &mut Cx, frame: ServerFrame) -> Result<ServerFrame> {
204 if frame.kind != FrameKind::Request {
205 return Err(Error::Eval(format!(
206 "tool {} cannot answer frame kind {}",
207 self.symbol,
208 frame.kind.as_symbol()
209 )));
210 }
211 let consistency = frame.envelope.consistency;
212 let request = eval_request_from_frame(cx, &frame)?;
213 let args = expr_args_to_values(cx, request.expr)?;
214 let value = self.call_values(cx, args)?;
215 let diagnostics = cx.take_diagnostics();
216 server_frame_from_reply(
217 cx,
218 &frame.codec,
219 EvalReply {
220 value,
221 diagnostics,
222 trace: None,
223 },
224 consistency,
225 )
226 }
227
228 fn as_any(&self) -> &dyn Any {
229 self
230 }
231}
232
233impl Component for Tool {
234 fn kind(&self) -> ComponentKind {
235 ComponentKind::Tool
236 }
237
238 fn name(&self) -> &Symbol {
239 &self.symbol
240 }
241
242 fn capabilities(&self) -> &[CapabilityName] {
243 &self.capabilities
244 }
245
246 fn reflect(&self, cx: &mut Cx) -> Result<Expr> {
247 self.metadata_value(cx)?.object().as_expr(cx)
248 }
249}
250
251#[derive(Default)]
252pub(crate) struct ToolFilter {
253 pub(crate) category: Option<Symbol>,
254 pub(crate) agent: Option<Symbol>,
255}
256
257pub(crate) fn define_tool(cx: &mut Cx, exprs: Vec<Expr>) -> Result<Value> {
258 let Some((name_expr, rest)) = exprs.split_first() else {
259 return Err(Error::Eval(
260 "agent/defun expects a name, metadata options, and a callable body".to_owned(),
261 ));
262 };
263 let name = symbol_of(name_expr, "agent/defun expects a symbol name")?;
264 if rest.len() < 2 {
265 return Err(Error::Eval(
266 "agent/defun expects metadata options followed by a callable body".to_owned(),
267 ));
268 }
269 let (body_expr, option_exprs) = rest
270 .split_last()
271 .ok_or_else(|| Error::Eval("agent/defun requires a callable body".to_owned()))?;
272 if !option_exprs.len().is_multiple_of(2) {
273 return Err(Error::Eval(
274 "agent/defun options must be key/value pairs".to_owned(),
275 ));
276 }
277
278 let mut description = None;
279 let mut args_shape = None;
280 let mut result_shape = None;
281 let mut category = None;
282 let mut capabilities = Vec::new();
283
284 for pair in option_exprs.chunks(2) {
285 let key = keyword(&pair[0])?;
286 match key.as_str() {
287 "description" => {
288 description = Some(string_literal(
289 &pair[1],
290 "agent/defun :description expects a string",
291 )?);
292 }
293 "args" => {
294 args_shape = Some(shape_from_expr(cx, &pair[1], &name, "args")?);
295 }
296 "result" => {
297 result_shape = Some(shape_from_expr(cx, &pair[1], &name, "result")?);
298 }
299 "category" => {
300 category = Some(symbol_of(
301 literal_expr(&pair[1]),
302 "agent/defun :category expects a symbol",
303 )?);
304 }
305 "capable" => {
306 capabilities = parse_capabilities_expr(literal_expr(&pair[1]))?;
307 }
308 other => return Err(Error::Eval(format!("agent/defun: unknown option :{other}"))),
309 }
310 }
311
312 let function = cx.eval_expr(body_expr.clone())?;
313 if function.object().as_callable().is_none() {
314 return Err(Error::TypeMismatch {
315 expected: "callable",
316 found: "non-callable",
317 });
318 }
319
320 let tool = Arc::new(Tool {
321 symbol: name.clone(),
322 description: description
323 .ok_or_else(|| Error::Eval("agent/defun requires :description".to_owned()))?,
324 args_shape: args_shape
325 .ok_or_else(|| Error::Eval("agent/defun requires :args".to_owned()))?,
326 result_shape,
327 category: category.unwrap_or_else(|| Symbol::new("general")),
328 capabilities,
329 function,
330 address: ServerAddress::Local,
331 codecs: installed_codecs(cx),
332 });
333 let value = cx.factory().opaque(tool.clone())?;
334 register_tool(cx, tool, value.clone())?;
335 Ok(value)
336}
337
338pub(crate) fn parse_tools_exprs(cx: &mut Cx, exprs: Vec<Expr>) -> Result<Value> {
339 if exprs.is_empty() {
340 return list_tools_value(cx, ToolFilter::default());
341 }
342 if !exprs.len().is_multiple_of(2) {
343 return Err(Error::Eval(
344 "agent/tools options must be key/value pairs".to_owned(),
345 ));
346 }
347 let mut filter = ToolFilter::default();
348 for pair in exprs.chunks(2) {
349 let key = keyword(&pair[0])?;
350 match key.as_str() {
351 "category" => {
352 filter.category = Some(symbol_from_eval(
353 cx,
354 &pair[1],
355 "agent/tools :category expects a symbol",
356 )?);
357 }
358 "agent" => {
359 filter.agent = Some(symbol_from_eval(
360 cx,
361 &pair[1],
362 "agent/tools :agent expects a symbol",
363 )?);
364 }
365 other => return Err(Error::Eval(format!("agent/tools: unknown option :{other}"))),
366 }
367 }
368 list_tools_value(cx, filter)
369}
370
371pub(crate) fn call_tool_value(cx: &mut Cx, args: Args) -> Result<Value> {
372 let Some(target) = args.values().first() else {
373 return Err(Error::Eval(
374 "agent/call-tool expects a tool name or tool value".to_owned(),
375 ));
376 };
377 let tool_value = resolve_tool_value(cx, target.clone())?;
378 let tool = tool_value
379 .object()
380 .downcast_ref::<Tool>()
381 .ok_or(Error::TypeMismatch {
382 expected: "tool",
383 found: "non-tool",
384 })?;
385 tool.call_values(cx, args.values()[1..].to_vec())
386}
387
388pub(crate) fn list_tools_value(cx: &mut Cx, filter: ToolFilter) -> Result<Value> {
389 let tools = registered_tools(cx)?
390 .into_iter()
391 .filter(|tool| {
392 filter
393 .category
394 .as_ref()
395 .is_none_or(|category| &tool.category == category)
396 })
397 .filter(|_| filter.agent.is_none())
398 .map(|tool| tool.metadata_value(cx))
399 .collect::<Result<Vec<_>>>()?;
400 cx.factory().list(tools)
401}
402
403pub(crate) fn register_tool(cx: &mut Cx, tool: Arc<Tool>, value: Value) -> Result<()> {
404 let lib = Symbol::new(AGENT_LIB_ID);
405 cx.registry_mut()
406 .register_value_for_lib(&lib, tool.symbol.clone(), value)?;
407 cx.registry_mut().append_export_record(
408 &lib,
409 ExportRecord {
410 kind: tool_export_kind(),
411 symbol: tool.symbol.clone(),
412 state: ExportState::Resolved {
413 id: sim_kernel::RuntimeId::Value,
414 },
415 },
416 )?;
417 Ok(())
418}
419
420pub(crate) fn resolve_tool_by_symbol(cx: &mut Cx, symbol: &Symbol) -> Result<Tool> {
421 let tool_value = cx.resolve_value(symbol)?;
422 tool_value
423 .object()
424 .downcast_ref::<Tool>()
425 .cloned()
426 .ok_or(Error::TypeMismatch {
427 expected: "tool",
428 found: "non-tool",
429 })
430}
431
432fn resolve_tool_value(cx: &mut Cx, value: Value) -> Result<Value> {
433 if value.object().downcast_ref::<Tool>().is_some() {
434 return Ok(value);
435 }
436 let symbol = symbol_from_value(
437 cx,
438 value,
439 "agent/call-tool expects a tool symbol or tool value",
440 )?;
441 let tool_value = cx.resolve_value(&symbol)?;
442 if tool_value.object().downcast_ref::<Tool>().is_some() {
443 Ok(tool_value)
444 } else {
445 Err(Error::TypeMismatch {
446 expected: "tool",
447 found: "non-tool",
448 })
449 }
450}
451
452fn registered_tools(cx: &Cx) -> Result<Vec<Arc<Tool>>> {
453 let loaded = cx
454 .registry()
455 .lib(&Symbol::new(AGENT_LIB_ID))
456 .ok_or_else(|| Error::Lib("agent lib is not installed".to_owned()))?;
457 let tools = loaded
458 .exports
459 .iter()
460 .filter(|record| record.kind == tool_export_kind())
461 .filter_map(|record| cx.registry().value_by_symbol(&record.symbol))
462 .filter_map(|value| value.object().downcast_ref::<Tool>())
463 .cloned()
464 .map(Arc::new)
465 .collect();
466 Ok(tools)
467}
468
469fn expr_args_to_values(cx: &mut Cx, expr: Expr) -> Result<Vec<Value>> {
470 match expr {
471 Expr::List(items) | Expr::Vector(items) => items
472 .iter()
473 .map(|item| expr_to_value(cx, item))
474 .collect::<Result<Vec<_>>>(),
475 other => Ok(vec![expr_to_value(cx, &other)?]),
476 }
477}
478
479fn tool_export_kind_symbol() -> Symbol {
480 Symbol::new(TOOL_EXPORT_KIND)
481}
482
483pub(crate) fn tool_export_kind() -> ExportKind {
484 ExportKind::new(tool_export_kind_symbol())
485}