1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! The tool-registry seam: the general injection point a host supplies so the
//! server can perform a tool call on a client-driven run's behalf.
//!
//! # Why it is a seam, not baked in
//!
//! This mirrors the [`ModelExecutor`](crate::ModelExecutor) decision exactly.
//! The server owns the *mechanism* of a durable tool step (append the
//! write-ahead intent, dispatch the tool, append the completion, answer retries
//! and reconciliation from the log), but it must not own the *policy* of which
//! tools exist or what they do. Salvor is for anyone building on it, browser or
//! backend; aarg is only the first consumer, and its typst render tool is one
//! registration, nothing salvor knows about. So the tools are registered by the
//! embedding binary and injected, never hard-wired.
//!
//! `salvor serve` wires an EMPTY registry by default: it holds no tools, so a
//! tool-step for any name is a clean `unknown_tool` `404` with no intent
//! written. Another host (a future `aarg serve`) registers its render tool
//! here, and nothing consumer-specific leaks into salvor-server. Tests inject
//! scripted tools with execution counters.
//!
//! # The operator-declared effect
//!
//! A registered tool carries its own [`Effect`](salvor_core::Effect) through
//! [`DynTool::effect`]. That is the operator's declaration, fixed at
//! registration. The tool-step endpoint takes the effect from the registration
//! and never from the client, so a caller cannot up- or down-grade a `Write`
//! into a freely retried `Read`. The recorded intent's effect is always the
//! registry's.
//!
//! # Reusing `DynTool`
//!
//! The registry stores `salvor_tools::DynTool` trait objects rather than a
//! server-local trait. salvor-server already depends on `salvor-tools` (its
//! agent factory builds agents out of the same tools), and `DynTool` is the
//! type-erased, `Value`-in/`Value`-out contract the runtime already dispatches
//! every tool through. The contract layer that defines `DynTool`, `ToolCtx`,
//! `ToolOutcome`, and `ToolError` builds without the crate's `mcp` feature, so
//! reusing it drags in no MCP stack. A registry tool and an agent tool are then
//! the same abstraction, and the server-performed tool step dispatches exactly
//! as `RunCtx::tool_call` does.
use HashMap;
use Arc;
use DynTool;
/// A set of named tools the server may dispatch on a client-driven run's
/// behalf. Injected by the embedding binary, exactly like
/// [`ModelExecutor`](crate::ModelExecutor).
///
/// Each tool is keyed by its [`DynTool::name`]. A later registration under a
/// name already present replaces the earlier tool, so a host composing a
/// registry keeps the last word.