1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ToolScheduling {
16 #[default]
18 Parallel,
19 Serial,
21}
22
23fn default_tool_scheduling() -> ToolScheduling {
24 ToolScheduling::default()
25}
26
27fn is_default_tool_scheduling(mode: &ToolScheduling) -> bool {
28 *mode == ToolScheduling::default()
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
38#[serde(tag = "type", rename_all = "snake_case")]
39pub enum ToolRetryPolicy {
40 #[default]
42 Never,
43 Safe {
45 max_attempts: u32,
46 base_delay_ms: u64,
47 max_delay_ms: u64,
48 },
49 Idempotent {
52 max_attempts: u32,
53 base_delay_ms: u64,
54 max_delay_ms: u64,
55 },
56}
57
58impl ToolRetryPolicy {
59 pub fn safe(max_attempts: u32, base_delay_ms: u64, max_delay_ms: u64) -> Self {
60 Self::Safe {
61 max_attempts,
62 base_delay_ms,
63 max_delay_ms,
64 }
65 }
66
67 pub fn idempotent(max_attempts: u32, base_delay_ms: u64, max_delay_ms: u64) -> Self {
68 Self::Idempotent {
69 max_attempts,
70 base_delay_ms,
71 max_delay_ms,
72 }
73 }
74
75 pub fn max_attempts(self) -> u32 {
76 match self {
77 Self::Never => 1,
78 Self::Safe { max_attempts, .. } | Self::Idempotent { max_attempts, .. } => {
79 max_attempts.max(1)
80 }
81 }
82 }
83
84 pub fn delay_ms_for_retry(self, retry_index: u32, requested_after_ms: Option<u64>) -> u64 {
85 let (base_delay_ms, max_delay_ms) = match self {
86 Self::Never => return 0,
87 Self::Safe {
88 base_delay_ms,
89 max_delay_ms,
90 ..
91 }
92 | Self::Idempotent {
93 base_delay_ms,
94 max_delay_ms,
95 ..
96 } => (base_delay_ms, max_delay_ms),
97 };
98 let multiplier = 1_u64.checked_shl(retry_index).unwrap_or(u64::MAX);
99 let backoff = base_delay_ms.saturating_mul(multiplier);
100 let delay = requested_after_ms.unwrap_or(backoff);
101 if max_delay_ms == 0 {
102 delay
103 } else {
104 delay.min(max_delay_ms)
105 }
106 }
107
108 pub fn requires_replay_key(self) -> bool {
109 matches!(self, Self::Idempotent { .. })
110 }
111}
112
113fn default_tool_retry_policy() -> ToolRetryPolicy {
114 ToolRetryPolicy::default()
115}
116
117fn is_default_tool_retry_policy(policy: &ToolRetryPolicy) -> bool {
118 *policy == ToolRetryPolicy::default()
119}
120
121#[derive(
122 Clone,
123 Copy,
124 Debug,
125 Default,
126 PartialEq,
127 Eq,
128 PartialOrd,
129 Ord,
130 serde::Serialize,
131 serde::Deserialize,
132)]
133#[serde(rename_all = "snake_case")]
134pub enum ToolAvailability {
135 #[default]
141 Off,
142 Searchable,
145 Callable,
148 Showcased,
151}
152
153impl ToolAvailability {
154 pub fn is_searchable(self) -> bool {
155 self >= Self::Searchable
156 }
157
158 pub fn is_callable(self) -> bool {
159 self >= Self::Callable
160 }
161
162 pub fn is_showcased(self) -> bool {
163 self >= Self::Showcased
164 }
165}
166
167#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
168pub struct ToolAvailabilityConfig {
169 pub base: ToolAvailability,
170}
171
172impl ToolAvailabilityConfig {
173 pub fn same(availability: ToolAvailability) -> Self {
174 Self { base: availability }
175 }
176
177 pub fn showcased() -> Self {
178 Self::same(ToolAvailability::Showcased)
179 }
180
181 pub fn callable() -> Self {
182 Self::same(ToolAvailability::Callable)
183 }
184
185 pub fn off() -> Self {
186 Self::same(ToolAvailability::Off)
187 }
188
189 pub fn base(&self) -> ToolAvailability {
190 self.base
191 }
192}
193
194impl Default for ToolAvailabilityConfig {
195 fn default() -> Self {
196 Self::showcased()
197 }
198}
199
200#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
201#[serde(rename_all = "snake_case")]
202pub enum ToolActivation {
203 #[default]
204 Always,
205 Internal,
206}
207
208fn is_default_tool_availability_config(config: &ToolAvailabilityConfig) -> bool {
209 *config == ToolAvailabilityConfig::default()
210}
211
212fn is_default_tool_activation(activation: &ToolActivation) -> bool {
213 *activation == ToolActivation::default()
214}
215
216#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
217#[serde(tag = "kind", rename_all = "snake_case")]
218pub enum ToolOutputContract {
219 #[default]
220 Static,
221 FromInputSchema {
222 input_field: String,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 default_schema: Option<serde_json::Value>,
225 },
226}
227
228impl ToolOutputContract {
229 pub fn from_input_schema(
230 input_field: impl Into<String>,
231 default_schema: Option<serde_json::Value>,
232 ) -> Self {
233 Self::FromInputSchema {
234 input_field: input_field.into(),
235 default_schema,
236 }
237 }
238
239 pub fn is_static(&self) -> bool {
240 matches!(self, Self::Static)
241 }
242
243 fn return_type_label(&self, static_schema: &serde_json::Value) -> String {
244 match self {
245 Self::Static => compact_schema_label(static_schema),
246 Self::FromInputSchema { .. } => "T".to_string(),
247 }
248 }
249
250 fn type_parameter_suffix(&self) -> Option<String> {
251 match self {
252 Self::Static => None,
253 Self::FromInputSchema { default_schema, .. } => {
254 let default = default_schema
255 .as_ref()
256 .map(compact_schema_label)
257 .unwrap_or_else(|| "any".to_string());
258 Some(format!("<T = {default}>"))
259 }
260 }
261 }
262
263 fn apply_type_witness_parameter(&self, params: &mut [ParameterDoc]) {
264 let Self::FromInputSchema { input_field, .. } = self else {
265 return;
266 };
267 if let Some(param) = params.iter_mut().find(|param| param.name == *input_field) {
268 param.type_label = "TypeSpec<T>".to_string();
269 param.nullable = false;
270 param.default_value = None;
271 param.enum_values.clear();
272 param.minimum = None;
273 param.maximum = None;
274 param.min_length = None;
275 param.max_length = None;
276 param.min_items = None;
277 param.max_items = None;
278 param.item_type = None;
279 }
280 }
281
282 fn return_fields(&self, static_schema: &serde_json::Value) -> Vec<serde_json::Value> {
283 match self {
284 Self::Static => return_field_metadata(static_schema),
285 Self::FromInputSchema { .. } => Vec::new(),
286 }
287 }
288}
289
290#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
291#[serde(tag = "kind", rename_all = "snake_case")]
292pub enum ToolArgumentProjectionPolicy {
293 #[default]
294 MaterializeProjectedValues,
295 PreserveProjectedRefsInField {
296 field: String,
297 },
298}
299
300impl ToolArgumentProjectionPolicy {
301 pub fn preserve_projected_refs_in_field(field: impl Into<String>) -> Self {
302 Self::PreserveProjectedRefsInField {
303 field: field.into(),
304 }
305 }
306
307 pub fn is_materialize_projected_values(&self) -> bool {
308 matches!(self, Self::MaterializeProjectedValues)
309 }
310}
311
312fn is_default_tool_argument_projection_policy(policy: &ToolArgumentProjectionPolicy) -> bool {
313 policy.is_materialize_projected_values()
314}
315
316#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
317#[serde(transparent)]
318pub struct ToolId(String);
319
320impl ToolId {
321 pub fn new(id: impl Into<String>) -> Self {
322 let id = id.into();
323 assert!(!id.trim().is_empty(), "tool id must not be empty");
324 Self(id)
325 }
326
327 pub fn as_str(&self) -> &str {
328 &self.0
329 }
330}
331
332impl<'de> serde::Deserialize<'de> for ToolId {
333 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334 where
335 D: serde::Deserializer<'de>,
336 {
337 let id = <String as serde::Deserialize>::deserialize(deserializer)?;
338 if id.trim().is_empty() {
339 return Err(serde::de::Error::custom("tool id must not be empty"));
340 }
341 Ok(Self(id))
342 }
343}
344
345impl From<String> for ToolId {
346 fn from(id: String) -> Self {
347 Self::new(id)
348 }
349}
350
351impl From<&str> for ToolId {
352 fn from(id: &str) -> Self {
353 Self::new(id)
354 }
355}
356
357impl std::fmt::Display for ToolId {
358 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359 f.write_str(&self.0)
360 }
361}
362
363#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
367pub struct ToolManifest {
368 pub id: ToolId,
369 pub name: String,
370 #[serde(default, skip_serializing_if = "String::is_empty")]
371 pub description: String,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub compact_contract: Option<CompactToolContract>,
374 #[serde(default, skip_serializing_if = "is_default_tool_availability_config")]
375 pub availability: ToolAvailabilityConfig,
376 #[serde(default, skip_serializing_if = "is_default_tool_activation")]
377 pub activation: ToolActivation,
378 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub availability_override: Option<ToolAvailability>,
380 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
381 pub bindings: std::collections::BTreeMap<String, serde_json::Value>,
382 #[serde(
383 default,
384 skip_serializing_if = "is_default_tool_argument_projection_policy"
385 )]
386 pub argument_projection: ToolArgumentProjectionPolicy,
387 #[serde(
388 default = "default_tool_scheduling",
389 skip_serializing_if = "is_default_tool_scheduling"
390 )]
391 pub scheduling: ToolScheduling,
392 #[serde(
393 default = "default_tool_retry_policy",
394 skip_serializing_if = "is_default_tool_retry_policy"
395 )]
396 pub retry_policy: ToolRetryPolicy,
397}
398
399impl ToolManifest {
400 pub fn effective_availability(&self) -> ToolAvailability {
401 self.availability_override
402 .unwrap_or_else(|| self.availability.base())
403 }
404}
405
406#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
408pub struct ToolContract {
409 #[serde(default = "ToolContract::default_input_schema")]
410 pub input_schema: serde_json::Value,
411 #[serde(default)]
412 pub output_schema: serde_json::Value,
413 #[serde(default, skip_serializing_if = "Vec::is_empty")]
414 pub input_schema_projections: Vec<SchemaProjectionOverride>,
415 #[serde(default, skip_serializing_if = "Vec::is_empty")]
416 pub output_schema_projections: Vec<SchemaProjectionOverride>,
417 #[serde(default, skip_serializing_if = "ToolOutputContract::is_static")]
418 pub output_contract: ToolOutputContract,
419 #[serde(default, skip_serializing_if = "Vec::is_empty")]
420 pub examples: Vec<String>,
421}
422
423impl Default for ToolContract {
424 fn default() -> Self {
425 Self {
426 input_schema: Self::default_input_schema(),
427 output_schema: serde_json::Value::Null,
428 input_schema_projections: Vec::new(),
429 output_schema_projections: Vec::new(),
430 output_contract: ToolOutputContract::Static,
431 examples: Vec::new(),
432 }
433 }
434}
435
436impl ToolContract {
437 pub fn default_input_schema() -> serde_json::Value {
438 serde_json::json!({
439 "type": "object",
440 "properties": {},
441 "additionalProperties": true
442 })
443 }
444
445 pub fn compact_contract(&self, manifest: &ToolManifest) -> CompactToolContract {
446 self.compact_contract_with_example_limit(manifest, COMPACT_TOOL_EXAMPLE_LIMIT)
447 }
448
449 pub fn compact_contract_with_example_limit(
450 &self,
451 manifest: &ToolManifest,
452 example_limit: usize,
453 ) -> CompactToolContract {
454 self.compact_contract_with_signature_name_and_example_limit(
455 manifest,
456 &manifest.name,
457 example_limit,
458 )
459 }
460
461 pub fn compact_contract_with_signature_name(
462 &self,
463 manifest: &ToolManifest,
464 signature_name: &str,
465 ) -> CompactToolContract {
466 self.compact_contract_with_signature_name_and_example_limit(
467 manifest,
468 signature_name,
469 COMPACT_TOOL_EXAMPLE_LIMIT,
470 )
471 }
472
473 pub fn compact_contract_with_signature_name_and_example_limit(
474 &self,
475 manifest: &ToolManifest,
476 signature_name: &str,
477 example_limit: usize,
478 ) -> CompactToolContract {
479 CompactToolContract {
480 name: signature_name.to_string(),
481 signature: self.input_signature_with_name(manifest, signature_name),
482 returns: self.output_summary(),
483 parameters: self.parameter_metadata(),
484 return_fields: self.output_contract.return_fields(&self.output_schema),
485 description: manifest.description.trim().to_string(),
486 examples: compact_examples(&self.examples, example_limit),
487 }
488 }
489
490 pub fn input_signature(&self, manifest: &ToolManifest) -> String {
491 self.input_signature_with_name(manifest, &manifest.name)
492 }
493
494 pub fn input_signature_with_name(
495 &self,
496 _manifest: &ToolManifest,
497 signature_name: &str,
498 ) -> String {
499 let params = self
500 .parameter_docs()
501 .into_iter()
502 .map(|p| p.signature_fragment())
503 .collect::<Vec<_>>();
504 let body = if params.is_empty() {
505 "{}".to_string()
506 } else {
507 format!("{{ {} }}", params.join(", "))
508 };
509 format!(
510 "{}{}({})",
511 signature_name,
512 self.output_contract
513 .type_parameter_suffix()
514 .unwrap_or_default(),
515 body
516 )
517 }
518
519 pub fn output_summary(&self) -> String {
520 self.output_contract.return_type_label(&self.output_schema)
521 }
522
523 pub fn parameter_metadata(&self) -> Vec<serde_json::Value> {
524 self.parameter_docs()
525 .into_iter()
526 .map(|param| param.into_value())
527 .collect()
528 }
529
530 pub fn model_tool(&self, manifest: &ToolManifest) -> ModelTool {
531 ModelTool {
532 name: manifest.name.clone(),
533 description: manifest.description.clone(),
534 input_schema: self.input_schema.clone(),
535 output_schema: self.output_schema.clone(),
536 input_schema_projections: self.input_schema_projections.clone(),
537 output_schema_projections: self.output_schema_projections.clone(),
538 }
539 }
540
541 fn parameter_docs(&self) -> Vec<ParameterDoc> {
542 let mut params = schema_parameter_docs(&self.input_schema);
543 self.output_contract
544 .apply_type_witness_parameter(&mut params);
545 params
546 }
547}
548
549#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
555pub struct ToolDefinition {
556 #[serde(flatten)]
557 pub manifest: ToolManifest,
558 #[serde(flatten)]
559 pub contract: ToolContract,
560}
561
562#[derive(Clone, Debug, PartialEq, Eq)]
563pub struct ModelTool {
564 pub name: String,
565 pub description: String,
566 pub input_schema: serde_json::Value,
567 pub output_schema: serde_json::Value,
568 pub input_schema_projections: Vec<SchemaProjectionOverride>,
569 pub output_schema_projections: Vec<SchemaProjectionOverride>,
570}
571
572#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
573pub struct SchemaProjectionOverride {
574 pub profile: String,
575 pub schema: serde_json::Value,
576}
577
578const COMPACT_TOOL_EXAMPLE_LIMIT: usize = 2;
579const COMPACT_TOOL_EXAMPLE_CHAR_LIMIT: usize = 240;
580
581#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
582pub struct CompactToolContract {
583 pub name: String,
584 pub signature: String,
585 pub returns: String,
586 #[serde(default, skip_serializing_if = "Vec::is_empty")]
587 pub parameters: Vec<serde_json::Value>,
588 #[serde(default, skip_serializing_if = "Vec::is_empty")]
589 pub return_fields: Vec<serde_json::Value>,
590 #[serde(default, skip_serializing_if = "String::is_empty")]
591 pub description: String,
592 #[serde(default, skip_serializing_if = "Vec::is_empty")]
593 pub examples: Vec<String>,
594}
595
596impl CompactToolContract {
597 pub fn render_signature_head(&self) -> String {
598 format!("{} -> {}", self.signature.trim(), self.returns.trim())
599 }
600
601 pub fn render_signature(&self) -> String {
602 let mut sections = vec![self.render_signature_head()];
603 let parameter_lines = self
604 .parameters
605 .iter()
606 .filter_map(compact_doc_line)
607 .collect::<Vec<_>>();
608 if !parameter_lines.is_empty() {
609 sections.push(format!("Parameters:\n{}", parameter_lines.join("\n")));
610 }
611 let return_field_lines = self
612 .return_fields
613 .iter()
614 .filter_map(compact_doc_line)
615 .collect::<Vec<_>>();
616 if !return_field_lines.is_empty() {
617 sections.push(format!("Return fields:\n{}", return_field_lines.join("\n")));
618 }
619 sections.join("\n")
620 }
621
622 pub fn render_returns(&self) -> String {
623 let mut sections = Vec::new();
624 let return_field_lines = self
625 .return_fields
626 .iter()
627 .filter_map(compact_doc_line)
628 .collect::<Vec<_>>();
629 if !return_field_lines.is_empty() {
630 sections.push(format!("Return fields:\n{}", return_field_lines.join("\n")));
631 }
632 sections.join("\n")
633 }
634
635 pub fn render_markdown(&self) -> String {
636 let mut sections = vec![format!("### {}", self.render_signature_head())];
637 if !self.description.trim().is_empty() {
638 sections.push(self.description.trim().to_string());
639 }
640 if !self.parameters.is_empty() {
641 sections.push(format!(
642 "Parameters:\n{}",
643 self.parameters
644 .iter()
645 .filter_map(compact_doc_line)
646 .collect::<Vec<_>>()
647 .join("\n")
648 ));
649 }
650 if !self.return_fields.is_empty() {
651 sections.push(format!(
652 "Return fields:\n{}",
653 self.return_fields
654 .iter()
655 .filter_map(compact_doc_line)
656 .collect::<Vec<_>>()
657 .join("\n")
658 ));
659 }
660 if !self.examples.is_empty() {
661 sections.push(format!("Examples: {}", self.examples.join("; ")));
662 }
663 sections.join("\n")
664 }
665}
666
667impl ToolDefinition {
668 pub fn raw(
669 id: impl Into<ToolId>,
670 name: impl Into<String>,
671 description: impl Into<String>,
672 input_schema: serde_json::Value,
673 output_schema: serde_json::Value,
674 ) -> Self {
675 Self {
676 manifest: ToolManifest {
677 id: id.into(),
678 name: name.into(),
679 description: description.into(),
680 compact_contract: None,
681 availability: ToolAvailabilityConfig::default(),
682 activation: ToolActivation::default(),
683 availability_override: None,
684 bindings: std::collections::BTreeMap::new(),
685 argument_projection: ToolArgumentProjectionPolicy::default(),
686 scheduling: default_tool_scheduling(),
687 retry_policy: default_tool_retry_policy(),
688 },
689 contract: ToolContract {
690 input_schema,
691 output_schema,
692 ..ToolContract::default()
693 },
694 }
695 }
696
697 pub fn typed<Args, Output>(
698 id: impl Into<ToolId>,
699 name: impl Into<String>,
700 description: impl Into<String>,
701 ) -> Self
702 where
703 Args: schemars::JsonSchema,
704 Output: schemars::JsonSchema,
705 {
706 Self::raw(
707 id,
708 name,
709 description,
710 schema_for::<Args>(),
711 schema_for::<Output>(),
712 )
713 }
714
715 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
716 self.contract.examples = examples;
717 self
718 }
719
720 pub fn with_availability(mut self, availability: ToolAvailabilityConfig) -> Self {
721 self.manifest.availability = availability;
722 self
723 }
724
725 pub fn with_activation(mut self, activation: ToolActivation) -> Self {
726 self.manifest.activation = activation;
727 self
728 }
729
730 pub fn with_argument_projection(
731 mut self,
732 argument_projection: ToolArgumentProjectionPolicy,
733 ) -> Self {
734 self.manifest.argument_projection = argument_projection;
735 self
736 }
737
738 pub fn with_scheduling(mut self, scheduling: ToolScheduling) -> Self {
739 self.manifest.scheduling = scheduling;
740 self
741 }
742
743 pub fn with_retry_policy(mut self, retry_policy: ToolRetryPolicy) -> Self {
744 self.manifest.retry_policy = retry_policy;
745 self
746 }
747
748 pub fn with_output_contract(mut self, output_contract: ToolOutputContract) -> Self {
749 self.contract.output_contract = output_contract;
750 self
751 }
752
753 pub fn with_input_schema_projection(
754 mut self,
755 profile: impl Into<String>,
756 schema: serde_json::Value,
757 ) -> Self {
758 let profile = profile.into();
759 self.contract
760 .input_schema_projections
761 .retain(|projection| projection.profile != profile);
762 self.contract
763 .input_schema_projections
764 .push(SchemaProjectionOverride { profile, schema });
765 self
766 }
767
768 pub fn with_output_schema_projection(
769 mut self,
770 profile: impl Into<String>,
771 schema: serde_json::Value,
772 ) -> Self {
773 let profile = profile.into();
774 self.contract
775 .output_schema_projections
776 .retain(|projection| projection.profile != profile);
777 self.contract
778 .output_schema_projections
779 .push(SchemaProjectionOverride { profile, schema });
780 self
781 }
782
783 pub fn with_output_from_input_schema(
784 self,
785 input_field: impl Into<String>,
786 default_schema: Option<serde_json::Value>,
787 ) -> Self {
788 self.with_output_contract(ToolOutputContract::from_input_schema(
789 input_field,
790 default_schema,
791 ))
792 }
793
794 pub fn default_input_schema() -> serde_json::Value {
795 ToolContract::default_input_schema()
796 }
797
798 pub fn id(&self) -> &ToolId {
801 &self.manifest.id
802 }
803
804 pub fn name(&self) -> &str {
806 &self.manifest.name
807 }
808
809 pub fn description(&self) -> &str {
811 &self.manifest.description
812 }
813
814 pub fn input_signature(&self) -> String {
815 self.contract.input_signature(&self.manifest)
816 }
817
818 pub fn output_summary(&self) -> String {
819 self.contract.output_summary()
820 }
821
822 pub fn signature(&self) -> String {
823 format!("{} -> {}", self.input_signature(), self.output_summary())
824 }
825
826 pub fn compact_contract(&self) -> CompactToolContract {
827 self.compact_contract_with_example_limit(COMPACT_TOOL_EXAMPLE_LIMIT)
828 }
829
830 pub fn compact_contract_with_example_limit(&self, example_limit: usize) -> CompactToolContract {
831 self.contract
832 .compact_contract_with_example_limit(&self.manifest, example_limit)
833 }
834
835 pub fn effective_availability(&self) -> ToolAvailability {
836 self.manifest.effective_availability()
837 }
838
839 pub fn model_tool(&self) -> ModelTool {
840 self.contract.model_tool(&self.manifest)
841 }
842
843 pub fn manifest(&self) -> ToolManifest {
846 let mut manifest = self.manifest.clone();
847 manifest.compact_contract = Some(self.contract.compact_contract(&manifest));
848 manifest
849 }
850
851 pub fn contract(&self) -> ToolContract {
852 self.contract.clone()
853 }
854
855 pub fn from_parts(manifest: ToolManifest, contract: ToolContract) -> Self {
858 Self { manifest, contract }
859 }
860
861 pub fn format_tool_docs(tools: &[ToolDefinition]) -> String {
862 Self::format_tool_docs_iter(tools.iter())
863 }
864
865 pub fn format_tool_docs_iter<'a>(
866 tools: impl IntoIterator<Item = &'a ToolDefinition>,
867 ) -> String {
868 tools
869 .into_iter()
870 .map(|tool| tool.compact_contract().render_markdown())
871 .collect::<Vec<_>>()
872 .join("\n\n")
873 }
874
875 pub fn parameter_metadata(&self) -> Vec<serde_json::Value> {
876 self.parameter_docs()
877 .into_iter()
878 .map(|param| param.into_value())
879 .collect()
880 }
881
882 fn parameter_docs(&self) -> Vec<ParameterDoc> {
883 let mut params = schema_parameter_docs(&self.contract.input_schema);
884 self.contract
885 .output_contract
886 .apply_type_witness_parameter(&mut params);
887 params
888 }
889}
890
891mod schema_docs;
892pub use schema_docs::schema_for;
893use schema_docs::{
894 ParameterDoc, compact_doc_line, compact_examples, compact_schema_label, return_field_metadata,
895 schema_parameter_docs,
896};
897
898mod schema_validation;
899pub use schema_validation::{LashSchema, validate_tool_input};
900
901include!("tool_contract/tests.rs");