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(
317 Clone,
318 Debug,
319 Default,
320 PartialEq,
321 Eq,
322 PartialOrd,
323 Ord,
324 Hash,
325 serde::Serialize,
326 serde::Deserialize,
327)]
328#[serde(transparent)]
329pub struct ToolId(String);
330
331impl ToolId {
332 pub fn new(id: impl Into<String>) -> Self {
333 let id = id.into();
334 assert!(!id.trim().is_empty(), "tool id must not be empty");
335 Self(id)
336 }
337
338 pub fn default_for_name(name: &str) -> Self {
339 Self::new(format!("tool:{name}"))
340 }
341
342 pub fn as_str(&self) -> &str {
343 &self.0
344 }
345}
346
347impl From<String> for ToolId {
348 fn from(id: String) -> Self {
349 Self::new(id)
350 }
351}
352
353impl From<&str> for ToolId {
354 fn from(id: &str) -> Self {
355 Self::new(id)
356 }
357}
358
359impl std::fmt::Display for ToolId {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 f.write_str(&self.0)
362 }
363}
364
365#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
369pub struct ToolManifest {
370 pub id: ToolId,
371 pub name: String,
372 #[serde(default, skip_serializing_if = "String::is_empty")]
373 pub description: String,
374 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub compact_contract: Option<CompactToolContract>,
376 #[serde(default, skip_serializing_if = "is_default_tool_availability_config")]
377 pub availability: ToolAvailabilityConfig,
378 #[serde(default, skip_serializing_if = "is_default_tool_activation")]
379 pub activation: ToolActivation,
380 #[serde(default, skip_serializing_if = "Option::is_none")]
381 pub availability_override: Option<ToolAvailability>,
382 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
383 pub bindings: std::collections::BTreeMap<String, serde_json::Value>,
384 #[serde(
385 default,
386 skip_serializing_if = "is_default_tool_argument_projection_policy"
387 )]
388 pub argument_projection: ToolArgumentProjectionPolicy,
389 #[serde(
390 default = "default_tool_scheduling",
391 skip_serializing_if = "is_default_tool_scheduling"
392 )]
393 pub scheduling: ToolScheduling,
394 #[serde(
395 default = "default_tool_retry_policy",
396 skip_serializing_if = "is_default_tool_retry_policy"
397 )]
398 pub retry_policy: ToolRetryPolicy,
399}
400
401impl ToolManifest {
402 pub fn effective_availability(&self) -> ToolAvailability {
403 self.availability_override
404 .unwrap_or_else(|| self.availability.base())
405 }
406}
407
408#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
410pub struct ToolContract {
411 #[serde(default = "ToolContract::default_input_schema")]
412 pub input_schema: serde_json::Value,
413 #[serde(default)]
414 pub output_schema: serde_json::Value,
415 #[serde(default, skip_serializing_if = "Vec::is_empty")]
416 pub input_schema_projections: Vec<SchemaProjectionOverride>,
417 #[serde(default, skip_serializing_if = "Vec::is_empty")]
418 pub output_schema_projections: Vec<SchemaProjectionOverride>,
419 #[serde(default, skip_serializing_if = "ToolOutputContract::is_static")]
420 pub output_contract: ToolOutputContract,
421 #[serde(default, skip_serializing_if = "Vec::is_empty")]
422 pub examples: Vec<String>,
423}
424
425impl Default for ToolContract {
426 fn default() -> Self {
427 Self {
428 input_schema: Self::default_input_schema(),
429 output_schema: serde_json::Value::Null,
430 input_schema_projections: Vec::new(),
431 output_schema_projections: Vec::new(),
432 output_contract: ToolOutputContract::Static,
433 examples: Vec::new(),
434 }
435 }
436}
437
438impl ToolContract {
439 pub fn default_input_schema() -> serde_json::Value {
440 serde_json::json!({
441 "type": "object",
442 "properties": {},
443 "additionalProperties": true
444 })
445 }
446
447 pub fn compact_contract(&self, manifest: &ToolManifest) -> CompactToolContract {
448 self.compact_contract_with_example_limit(manifest, COMPACT_TOOL_EXAMPLE_LIMIT)
449 }
450
451 pub fn compact_contract_with_example_limit(
452 &self,
453 manifest: &ToolManifest,
454 example_limit: usize,
455 ) -> CompactToolContract {
456 CompactToolContract {
457 name: manifest.name.clone(),
458 signature: self.input_signature(manifest),
459 returns: self.output_summary(),
460 parameters: self.parameter_metadata(),
461 return_fields: self.output_contract.return_fields(&self.output_schema),
462 description: manifest.description.trim().to_string(),
463 examples: compact_examples(&self.examples, example_limit),
464 }
465 }
466
467 pub fn input_signature(&self, manifest: &ToolManifest) -> String {
468 let params = self
469 .parameter_docs()
470 .into_iter()
471 .map(|p| p.signature_fragment())
472 .collect::<Vec<_>>();
473 let body = if params.is_empty() {
474 "{}".to_string()
475 } else {
476 format!("{{ {} }}", params.join(", "))
477 };
478 format!(
479 "{}{}({})",
480 manifest.name,
481 self.output_contract
482 .type_parameter_suffix()
483 .unwrap_or_default(),
484 body
485 )
486 }
487
488 pub fn output_summary(&self) -> String {
489 self.output_contract.return_type_label(&self.output_schema)
490 }
491
492 pub fn parameter_metadata(&self) -> Vec<serde_json::Value> {
493 self.parameter_docs()
494 .into_iter()
495 .map(|param| param.into_value())
496 .collect()
497 }
498
499 pub fn model_tool(&self, manifest: &ToolManifest) -> ModelTool {
500 ModelTool {
501 name: manifest.name.clone(),
502 description: manifest.description.clone(),
503 input_schema: self.input_schema.clone(),
504 output_schema: self.output_schema.clone(),
505 input_schema_projections: self.input_schema_projections.clone(),
506 output_schema_projections: self.output_schema_projections.clone(),
507 }
508 }
509
510 fn parameter_docs(&self) -> Vec<ParameterDoc> {
511 let mut params = schema_parameter_docs(&self.input_schema);
512 self.output_contract
513 .apply_type_witness_parameter(&mut params);
514 params
515 }
516}
517
518#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
524pub struct ToolDefinition {
525 #[serde(flatten)]
526 pub manifest: ToolManifest,
527 #[serde(flatten)]
528 pub contract: ToolContract,
529}
530
531#[derive(Clone, Debug, PartialEq, Eq)]
532pub struct ModelTool {
533 pub name: String,
534 pub description: String,
535 pub input_schema: serde_json::Value,
536 pub output_schema: serde_json::Value,
537 pub input_schema_projections: Vec<SchemaProjectionOverride>,
538 pub output_schema_projections: Vec<SchemaProjectionOverride>,
539}
540
541#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
542pub struct SchemaProjectionOverride {
543 pub profile: String,
544 pub schema: serde_json::Value,
545}
546
547const COMPACT_TOOL_EXAMPLE_LIMIT: usize = 2;
548const COMPACT_TOOL_EXAMPLE_CHAR_LIMIT: usize = 240;
549
550#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
551pub struct CompactToolContract {
552 pub name: String,
553 pub signature: String,
554 pub returns: String,
555 #[serde(default, skip_serializing_if = "Vec::is_empty")]
556 pub parameters: Vec<serde_json::Value>,
557 #[serde(default, skip_serializing_if = "Vec::is_empty")]
558 pub return_fields: Vec<serde_json::Value>,
559 #[serde(default, skip_serializing_if = "String::is_empty")]
560 pub description: String,
561 #[serde(default, skip_serializing_if = "Vec::is_empty")]
562 pub examples: Vec<String>,
563}
564
565impl CompactToolContract {
566 pub fn render_signature_head(&self) -> String {
567 format!("{} -> {}", self.signature.trim(), self.returns.trim())
568 }
569
570 pub fn render_signature(&self) -> String {
571 let mut sections = vec![self.render_signature_head()];
572 let parameter_lines = self
573 .parameters
574 .iter()
575 .filter_map(compact_doc_line)
576 .collect::<Vec<_>>();
577 if !parameter_lines.is_empty() {
578 sections.push(format!("Parameters:\n{}", parameter_lines.join("\n")));
579 }
580 let return_field_lines = self
581 .return_fields
582 .iter()
583 .filter_map(compact_doc_line)
584 .collect::<Vec<_>>();
585 if !return_field_lines.is_empty() {
586 sections.push(format!("Return fields:\n{}", return_field_lines.join("\n")));
587 }
588 sections.join("\n")
589 }
590
591 pub fn render_returns(&self) -> String {
592 let mut sections = Vec::new();
593 let return_field_lines = self
594 .return_fields
595 .iter()
596 .filter_map(compact_doc_line)
597 .collect::<Vec<_>>();
598 if !return_field_lines.is_empty() {
599 sections.push(format!("Return fields:\n{}", return_field_lines.join("\n")));
600 }
601 sections.join("\n")
602 }
603
604 pub fn render_markdown(&self) -> String {
605 let mut sections = vec![format!("### {}", self.render_signature_head())];
606 if !self.description.trim().is_empty() {
607 sections.push(self.description.trim().to_string());
608 }
609 if !self.parameters.is_empty() {
610 sections.push(format!(
611 "Parameters:\n{}",
612 self.parameters
613 .iter()
614 .filter_map(compact_doc_line)
615 .collect::<Vec<_>>()
616 .join("\n")
617 ));
618 }
619 if !self.return_fields.is_empty() {
620 sections.push(format!(
621 "Return fields:\n{}",
622 self.return_fields
623 .iter()
624 .filter_map(compact_doc_line)
625 .collect::<Vec<_>>()
626 .join("\n")
627 ));
628 }
629 if !self.examples.is_empty() {
630 sections.push(format!("Examples: {}", self.examples.join("; ")));
631 }
632 sections.join("\n")
633 }
634}
635
636impl ToolDefinition {
637 pub fn raw_with_id(
638 id: impl Into<ToolId>,
639 name: impl Into<String>,
640 description: impl Into<String>,
641 input_schema: serde_json::Value,
642 output_schema: serde_json::Value,
643 ) -> Self {
644 Self {
645 manifest: ToolManifest {
646 id: id.into(),
647 name: name.into(),
648 description: description.into(),
649 compact_contract: None,
650 ..ToolManifest::default()
651 },
652 contract: ToolContract {
653 input_schema,
654 output_schema,
655 ..ToolContract::default()
656 },
657 }
658 }
659
660 pub fn raw_named(
661 name: impl Into<String>,
662 description: impl Into<String>,
663 input_schema: serde_json::Value,
664 output_schema: serde_json::Value,
665 ) -> Self {
666 let name = name.into();
667 Self::raw_with_id(
668 ToolId::default_for_name(&name),
669 name,
670 description,
671 input_schema,
672 output_schema,
673 )
674 }
675
676 pub fn typed_with_id<Args, Output>(
677 id: impl Into<ToolId>,
678 name: impl Into<String>,
679 description: impl Into<String>,
680 ) -> Self
681 where
682 Args: schemars::JsonSchema,
683 Output: schemars::JsonSchema,
684 {
685 Self::raw_with_id(
686 id,
687 name,
688 description,
689 schema_for::<Args>(),
690 schema_for::<Output>(),
691 )
692 }
693
694 pub fn typed<Args, Output>(name: impl Into<String>, description: impl Into<String>) -> Self
695 where
696 Args: schemars::JsonSchema,
697 Output: schemars::JsonSchema,
698 {
699 let name = name.into();
700 Self::typed_with_id::<Args, Output>(ToolId::default_for_name(&name), name, description)
701 }
702
703 pub fn raw(
704 id: impl Into<ToolId>,
705 name: impl Into<String>,
706 description: impl Into<String>,
707 input_schema: serde_json::Value,
708 output_schema: serde_json::Value,
709 ) -> Self {
710 Self::raw_with_id(id, name, description, input_schema, output_schema)
711 }
712
713 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
714 self.contract.examples = examples;
715 self
716 }
717
718 pub fn with_availability(mut self, availability: ToolAvailabilityConfig) -> Self {
719 self.manifest.availability = availability;
720 self
721 }
722
723 pub fn with_activation(mut self, activation: ToolActivation) -> Self {
724 self.manifest.activation = activation;
725 self
726 }
727
728 pub fn with_argument_projection(
729 mut self,
730 argument_projection: ToolArgumentProjectionPolicy,
731 ) -> Self {
732 self.manifest.argument_projection = argument_projection;
733 self
734 }
735
736 pub fn with_scheduling(mut self, scheduling: ToolScheduling) -> Self {
737 self.manifest.scheduling = scheduling;
738 self
739 }
740
741 pub fn with_retry_policy(mut self, retry_policy: ToolRetryPolicy) -> Self {
742 self.manifest.retry_policy = retry_policy;
743 self
744 }
745
746 pub fn with_output_contract(mut self, output_contract: ToolOutputContract) -> Self {
747 self.contract.output_contract = output_contract;
748 self
749 }
750
751 pub fn with_input_schema_projection(
752 mut self,
753 profile: impl Into<String>,
754 schema: serde_json::Value,
755 ) -> Self {
756 let profile = profile.into();
757 self.contract
758 .input_schema_projections
759 .retain(|projection| projection.profile != profile);
760 self.contract
761 .input_schema_projections
762 .push(SchemaProjectionOverride { profile, schema });
763 self
764 }
765
766 pub fn with_output_schema_projection(
767 mut self,
768 profile: impl Into<String>,
769 schema: serde_json::Value,
770 ) -> Self {
771 let profile = profile.into();
772 self.contract
773 .output_schema_projections
774 .retain(|projection| projection.profile != profile);
775 self.contract
776 .output_schema_projections
777 .push(SchemaProjectionOverride { profile, schema });
778 self
779 }
780
781 pub fn with_output_from_input_schema(
782 self,
783 input_field: impl Into<String>,
784 default_schema: Option<serde_json::Value>,
785 ) -> Self {
786 self.with_output_contract(ToolOutputContract::from_input_schema(
787 input_field,
788 default_schema,
789 ))
790 }
791
792 pub fn default_input_schema() -> serde_json::Value {
793 ToolContract::default_input_schema()
794 }
795
796 pub fn id(&self) -> &ToolId {
799 &self.manifest.id
800 }
801
802 pub fn name(&self) -> &str {
804 &self.manifest.name
805 }
806
807 pub fn description(&self) -> &str {
809 &self.manifest.description
810 }
811
812 pub fn input_signature(&self) -> String {
813 self.contract.input_signature(&self.manifest)
814 }
815
816 pub fn output_summary(&self) -> String {
817 self.contract.output_summary()
818 }
819
820 pub fn signature(&self) -> String {
821 format!("{} -> {}", self.input_signature(), self.output_summary())
822 }
823
824 pub fn compact_contract(&self) -> CompactToolContract {
825 self.compact_contract_with_example_limit(COMPACT_TOOL_EXAMPLE_LIMIT)
826 }
827
828 pub fn compact_contract_with_example_limit(&self, example_limit: usize) -> CompactToolContract {
829 self.contract
830 .compact_contract_with_example_limit(&self.manifest, example_limit)
831 }
832
833 pub fn effective_availability(&self) -> ToolAvailability {
834 self.manifest.effective_availability()
835 }
836
837 pub fn model_tool(&self) -> ModelTool {
838 self.contract.model_tool(&self.manifest)
839 }
840
841 pub fn manifest(&self) -> ToolManifest {
844 let mut manifest = self.manifest.clone();
845 manifest.compact_contract = Some(self.contract.compact_contract(&manifest));
846 manifest
847 }
848
849 pub fn contract(&self) -> ToolContract {
850 self.contract.clone()
851 }
852
853 pub fn from_parts(manifest: ToolManifest, contract: ToolContract) -> Self {
856 Self { manifest, contract }
857 }
858
859 pub fn format_tool_docs(tools: &[ToolDefinition]) -> String {
860 Self::format_tool_docs_iter(tools.iter())
861 }
862
863 pub fn format_tool_docs_iter<'a>(
864 tools: impl IntoIterator<Item = &'a ToolDefinition>,
865 ) -> String {
866 tools
867 .into_iter()
868 .map(|tool| tool.compact_contract().render_markdown())
869 .collect::<Vec<_>>()
870 .join("\n\n")
871 }
872
873 pub fn parameter_metadata(&self) -> Vec<serde_json::Value> {
874 self.parameter_docs()
875 .into_iter()
876 .map(|param| param.into_value())
877 .collect()
878 }
879
880 fn parameter_docs(&self) -> Vec<ParameterDoc> {
881 let mut params = schema_parameter_docs(&self.contract.input_schema);
882 self.contract
883 .output_contract
884 .apply_type_witness_parameter(&mut params);
885 params
886 }
887}
888
889mod schema_docs;
890pub use schema_docs::schema_for;
891use schema_docs::{
892 ParameterDoc, compact_doc_line, compact_examples, compact_schema_label, return_field_metadata,
893 schema_parameter_docs,
894};
895
896mod schema_validation;
897pub use schema_validation::{LashSchema, validate_tool_input};
898
899include!("tool_contract/tests.rs");