1use agent_sdk_core::{
7 AgentError, CapabilityId, CapabilityNamespace, CapabilityPermission, ExecutorRef,
8 PackageSidecarRef, PolicyKind, PolicyRef, PrivacyClass, SourceRef, ToolPackId, ToolPackKind,
9 ToolPackSnapshot, ToolPackToolSnapshot, TrustClass,
10 policy::{EffectClass, RiskClass},
11 tool_records::CanonicalToolName,
12};
13
14use crate::packs::ToolkitPackBundle;
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub enum ToolkitToolExecutionMode {
21 Sync,
23 Async,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct Tool {
31 snapshot: ToolPackToolSnapshot,
32 mode: ToolkitToolExecutionMode,
33}
34
35impl Tool {
36 pub fn builder(
38 tool_name: impl Into<String>,
39 executor_ref: impl Into<String>,
40 schema_id: impl Into<String>,
41 policy_ref: PolicyRef,
42 ) -> ToolBuilder {
43 ToolBuilder::new(tool_name, executor_ref, schema_id, policy_ref)
44 }
45
46 pub fn canonical_tool_name(&self) -> &CanonicalToolName {
48 &self.snapshot.canonical_tool_name
49 }
50
51 pub fn executor_ref(&self) -> &ExecutorRef {
53 &self.snapshot.executor_ref
54 }
55
56 pub fn mode(&self) -> &ToolkitToolExecutionMode {
58 &self.mode
59 }
60
61 pub fn snapshot(&self) -> &ToolPackToolSnapshot {
63 &self.snapshot
64 }
65
66 pub fn into_snapshot(self) -> ToolPackToolSnapshot {
68 self.snapshot
69 }
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct AsyncTool {
77 snapshot: ToolPackToolSnapshot,
78 mode: ToolkitToolExecutionMode,
79}
80
81impl AsyncTool {
82 pub fn builder(
84 tool_name: impl Into<String>,
85 executor_ref: impl Into<String>,
86 schema_id: impl Into<String>,
87 policy_ref: PolicyRef,
88 ) -> ToolBuilder {
89 ToolBuilder::new(tool_name, executor_ref, schema_id, policy_ref).async_mode()
90 }
91
92 pub fn canonical_tool_name(&self) -> &CanonicalToolName {
94 &self.snapshot.canonical_tool_name
95 }
96
97 pub fn executor_ref(&self) -> &ExecutorRef {
99 &self.snapshot.executor_ref
100 }
101
102 pub fn mode(&self) -> &ToolkitToolExecutionMode {
104 &self.mode
105 }
106
107 pub fn snapshot(&self) -> &ToolPackToolSnapshot {
109 &self.snapshot
110 }
111
112 pub fn into_snapshot(self) -> ToolPackToolSnapshot {
114 self.snapshot
115 }
116}
117
118#[derive(Clone, Debug)]
119pub struct ToolBuilder {
123 tool_name: String,
124 executor_ref: String,
125 schema_id: String,
126 policy_refs: Vec<PolicyRef>,
127 capability_id: Option<CapabilityId>,
128 namespace: Option<CapabilityNamespace>,
129 required_permissions: Vec<CapabilityPermission>,
130 effect_class: Option<EffectClass>,
131 risk_class: Option<RiskClass>,
132 redaction_policy_ref: PolicyRef,
133 timeout_ms: u64,
134 cancellation: String,
135 reconciliation: String,
136 privacy: PrivacyClass,
137 mode: ToolkitToolExecutionMode,
138}
139
140impl ToolBuilder {
141 pub fn new(
143 tool_name: impl Into<String>,
144 executor_ref: impl Into<String>,
145 schema_id: impl Into<String>,
146 policy_ref: PolicyRef,
147 ) -> Self {
148 Self {
149 tool_name: tool_name.into(),
150 executor_ref: executor_ref.into(),
151 schema_id: schema_id.into(),
152 policy_refs: vec![policy_ref],
153 capability_id: None,
154 namespace: None,
155 required_permissions: Vec::new(),
156 effect_class: None,
157 risk_class: None,
158 redaction_policy_ref: PolicyRef::with_kind(
159 PolicyKind::Redaction,
160 "policy.redaction.tool_result.refs_only",
161 ),
162 timeout_ms: 10_000,
163 cancellation: "best_effort".to_string(),
164 reconciliation: "effect_lineage_required".to_string(),
165 privacy: PrivacyClass::ContentRefsOnly,
166 mode: ToolkitToolExecutionMode::Sync,
167 }
168 }
169
170 pub fn read_only(mut self) -> Self {
172 self.effect_class = Some(EffectClass::Read);
173 self.risk_class = Some(RiskClass::Low);
174 self
175 }
176
177 pub fn write_effect(mut self) -> Self {
179 self.effect_class = Some(EffectClass::Write);
180 self.risk_class = Some(RiskClass::High);
181 self
182 }
183
184 pub fn effect(mut self, effect_class: EffectClass, risk_class: RiskClass) -> Self {
186 self.effect_class = Some(effect_class);
187 self.risk_class = Some(risk_class);
188 self
189 }
190
191 pub fn capability_id(mut self, capability_id: CapabilityId) -> Self {
193 self.capability_id = Some(capability_id);
194 self
195 }
196
197 pub fn namespace(mut self, namespace: CapabilityNamespace) -> Self {
199 self.namespace = Some(namespace);
200 self
201 }
202
203 pub fn required_permission(mut self, permission: CapabilityPermission) -> Self {
205 self.required_permissions.push(permission);
206 self
207 }
208
209 pub fn policy_ref(mut self, policy_ref: PolicyRef) -> Self {
211 self.policy_refs.push(policy_ref);
212 self
213 }
214
215 pub fn redaction_policy(mut self, policy_ref: PolicyRef) -> Self {
217 self.redaction_policy_ref = policy_ref;
218 self
219 }
220
221 pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
223 self.timeout_ms = timeout_ms;
224 self
225 }
226
227 pub fn cancellation(mut self, cancellation: impl Into<String>) -> Self {
229 self.cancellation = cancellation.into();
230 self
231 }
232
233 pub fn reconciliation(mut self, reconciliation: impl Into<String>) -> Self {
235 self.reconciliation = reconciliation.into();
236 self
237 }
238
239 pub fn privacy(mut self, privacy: PrivacyClass) -> Self {
241 self.privacy = privacy;
242 self
243 }
244
245 pub fn async_mode(mut self) -> Self {
247 self.mode = ToolkitToolExecutionMode::Async;
248 self.timeout_ms = self.timeout_ms.max(60_000);
249 self.cancellation = "cooperative".to_string();
250 self
251 }
252
253 pub fn build(self) -> Result<Tool, AgentError> {
255 let mode = self.mode.clone();
256 Ok(Tool {
257 snapshot: self.build_snapshot()?,
258 mode,
259 })
260 }
261
262 pub fn build_async(self) -> Result<AsyncTool, AgentError> {
264 let builder = self.async_mode();
265 let mode = builder.mode.clone();
266 Ok(AsyncTool {
267 snapshot: builder.build_snapshot()?,
268 mode,
269 })
270 }
271
272 fn build_snapshot(self) -> Result<ToolPackToolSnapshot, AgentError> {
273 let effect_class = self
274 .effect_class
275 .ok_or_else(|| AgentError::missing_required_field("tool.effect_class"))?;
276 let risk_class = self
277 .risk_class
278 .ok_or_else(|| AgentError::missing_required_field("tool.risk_class"))?;
279 let capability_id = self
280 .capability_id
281 .unwrap_or_else(|| CapabilityId::new(format!("cap.tool.{}", self.tool_name)));
282 let namespace = self
283 .namespace
284 .unwrap_or_else(|| CapabilityNamespace::new(format!("tool.{}", self.tool_name)));
285 Ok(ToolPackToolSnapshot {
286 capability_id,
287 canonical_tool_name: CanonicalToolName::new(self.tool_name),
288 namespace,
289 schema_ref: PackageSidecarRef::new(self.schema_id, "tool_schema", "v1"),
290 executor_ref: ExecutorRef::new(self.executor_ref),
291 policy_refs: self.policy_refs,
292 required_permissions: self.required_permissions,
293 effect_class,
294 risk_class,
295 redaction_policy_ref: self.redaction_policy_ref,
296 timeout_ms: self.timeout_ms,
297 cancellation: self.cancellation,
298 reconciliation: self.reconciliation,
299 privacy: self.privacy,
300 })
301 }
302}
303
304#[derive(Clone, Debug)]
305pub struct ToolPackBuilder {
309 pack_id: ToolPackId,
310 kind: ToolPackKind,
311 version: String,
312 source: SourceRef,
313 trust: TrustClass,
314 tools: Vec<ToolPackToolSnapshot>,
315}
316
317impl ToolPackBuilder {
318 pub fn new(
320 pack_id: ToolPackId,
321 kind: ToolPackKind,
322 version: impl Into<String>,
323 source: SourceRef,
324 ) -> Self {
325 Self {
326 pack_id,
327 kind,
328 version: version.into(),
329 source,
330 trust: TrustClass::SdkGenerated,
331 tools: Vec::new(),
332 }
333 }
334
335 pub fn trust(mut self, trust: TrustClass) -> Self {
337 self.trust = trust;
338 self
339 }
340
341 pub fn listen(mut self, tool: Tool) -> Self {
344 self.tools.push(tool.into_snapshot());
345 self
346 }
347
348 pub fn listen_async(mut self, tool: AsyncTool) -> Self {
351 self.tools.push(tool.into_snapshot());
352 self
353 }
354
355 pub fn build(self) -> Result<ToolkitPackBundle, AgentError> {
358 let mut snapshot =
359 ToolPackSnapshot::new(self.pack_id, self.kind, self.version, self.source)
360 .with_trust(self.trust);
361 for tool in self.tools {
362 snapshot = snapshot.with_tool(tool);
363 }
364 ToolkitPackBundle::from_snapshot(snapshot)
365 }
366}