lodestone_client/
tooling.rs1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4use lodestone_wire::{DiscoverResult, LODESTONE_API_SCHEMA_ID, LODESTONE_API_SPEC_SHA256};
5use serde_json::Value;
6
7use crate::generated_tool_packs::{TOOL_INDEX_JSON, tool_pack_json};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ToolPackError {
11 DiscoverySchemaMismatch,
12 UnknownFamily,
13 GeneratedArtifactInvalid,
14}
15
16impl Display for ToolPackError {
17 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
18 formatter.write_str(match self {
19 Self::DiscoverySchemaMismatch => "native discovery schema does not match the packed client",
20 Self::UnknownFamily => "native tool capability family is unknown",
21 Self::GeneratedArtifactInvalid => "generated native tool artifact is invalid",
22 })
23 }
24}
25
26impl Error for ToolPackError {}
27
28pub fn load_tool_pack(
31 discovery: &DiscoverResult,
32 family: &str,
33 host_maximum_result_bytes: u64,
34) -> Result<Value, ToolPackError> {
35 if discovery.schema_id != LODESTONE_API_SCHEMA_ID || discovery.schema_digest.value != LODESTONE_API_SPEC_SHA256 {
36 return Err(ToolPackError::DiscoverySchemaMismatch);
37 }
38 let index: Value = serde_json::from_str(TOOL_INDEX_JSON).map_err(|_| ToolPackError::GeneratedArtifactInvalid)?;
39 if index.get("native_schema_sha256").and_then(Value::as_str) != Some(LODESTONE_API_SPEC_SHA256)
40 || !index.get("families").and_then(Value::as_array).is_some_and(|families| {
41 families
42 .iter()
43 .any(|entry| entry.get("id").and_then(Value::as_str) == Some(family))
44 })
45 {
46 return Err(ToolPackError::UnknownFamily);
47 }
48 let source = tool_pack_json(family).ok_or(ToolPackError::UnknownFamily)?;
49 let mut pack: Value = serde_json::from_str(source).map_err(|_| ToolPackError::GeneratedArtifactInvalid)?;
50 if pack.get("native_schema_sha256").and_then(Value::as_str) != Some(LODESTONE_API_SPEC_SHA256) {
51 return Err(ToolPackError::GeneratedArtifactInvalid);
52 }
53 let available = discovery
54 .capabilities
55 .iter()
56 .filter(|capability| capability.state == "available" && capability.capability_family == family)
57 .map(|capability| capability.command.as_str())
58 .collect::<std::collections::BTreeSet<_>>();
59 let operations = pack
60 .get_mut("operations")
61 .and_then(Value::as_array_mut)
62 .ok_or(ToolPackError::GeneratedArtifactInvalid)?;
63 operations.retain(|operation| {
64 let command = operation.get("command").and_then(Value::as_str);
65 let maximum = operation.get("maximum_result_bytes").and_then(Value::as_u64);
66 command.is_some_and(|command| available.contains(command))
67 && maximum.is_some_and(|maximum| maximum <= host_maximum_result_bytes)
68 });
69 Ok(pack)
70}
71
72pub fn describe_tool_operation(command: &str) -> Result<Option<Value>, ToolPackError> {
75 let index: Value = serde_json::from_str(TOOL_INDEX_JSON).map_err(|_| ToolPackError::GeneratedArtifactInvalid)?;
76 if index.get("native_schema_sha256").and_then(Value::as_str) != Some(LODESTONE_API_SPEC_SHA256) {
77 return Err(ToolPackError::GeneratedArtifactInvalid);
78 }
79 let Some(family) = index.get("families").and_then(Value::as_array).and_then(|families| {
80 families.iter().find(|family| {
81 family
82 .get("commands")
83 .and_then(Value::as_array)
84 .is_some_and(|commands| commands.iter().any(|value| value.as_str() == Some(command)))
85 })
86 }) else {
87 return Ok(None);
88 };
89 let family = family
90 .get("id")
91 .and_then(Value::as_str)
92 .ok_or(ToolPackError::GeneratedArtifactInvalid)?;
93 let source = tool_pack_json(family).ok_or(ToolPackError::GeneratedArtifactInvalid)?;
94 let pack: Value = serde_json::from_str(source).map_err(|_| ToolPackError::GeneratedArtifactInvalid)?;
95 if pack.get("native_schema_sha256").and_then(Value::as_str) != Some(LODESTONE_API_SPEC_SHA256) {
96 return Err(ToolPackError::GeneratedArtifactInvalid);
97 }
98 let mut operation = pack
99 .get("operations")
100 .and_then(Value::as_array)
101 .and_then(|operations| {
102 operations
103 .iter()
104 .find(|operation| operation.get("command").and_then(Value::as_str) == Some(command))
105 })
106 .cloned()
107 .ok_or(ToolPackError::GeneratedArtifactInvalid)?;
108 let definitions = pack
109 .get("$defs")
110 .cloned()
111 .ok_or(ToolPackError::GeneratedArtifactInvalid)?;
112 operation
113 .as_object_mut()
114 .ok_or(ToolPackError::GeneratedArtifactInvalid)?
115 .insert("$defs".to_owned(), definitions);
116 Ok(Some(operation))
117}