Syncing
This commit is contained in:
parent
e45a8063e3
commit
212fea579f
2 changed files with 535 additions and 1 deletions
531
src/tools.rs
531
src/tools.rs
|
|
@ -1,5 +1,21 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Context in which a tool should be available
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecutionContext {
|
||||
Reef,
|
||||
Krill,
|
||||
Both,
|
||||
Agent,
|
||||
}
|
||||
|
||||
impl Default for ExecutionContext {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDefinition {
|
||||
#[serde(default)]
|
||||
|
|
@ -22,6 +38,15 @@ pub struct ToolDefinition {
|
|||
/// Maximum execution time in milliseconds. Defaults to 30000 (30s).
|
||||
#[serde(default = "default_timeout_ms")]
|
||||
pub timeout_ms: u64,
|
||||
/// Whether this tool version is deprecated
|
||||
#[serde(default)]
|
||||
pub deprecated: bool,
|
||||
/// Message shown when tool is deprecated, explaining migration path
|
||||
#[serde(default)]
|
||||
pub deprecation_message: String,
|
||||
/// Execution context indicating where this tool should be available
|
||||
#[serde(default)]
|
||||
pub execution_context: ExecutionContext,
|
||||
}
|
||||
|
||||
const fn default_timeout_ms() -> u64 {
|
||||
|
|
@ -41,6 +66,9 @@ impl Default for ToolDefinition {
|
|||
version: String::new(),
|
||||
project_scoped: false,
|
||||
timeout_ms: default_timeout_ms(),
|
||||
deprecated: false,
|
||||
deprecation_message: String::new(),
|
||||
execution_context: ExecutionContext::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +105,72 @@ impl ToolDefinition {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this tool version is deprecated
|
||||
pub fn is_deprecated(&self) -> bool {
|
||||
self.deprecated
|
||||
}
|
||||
|
||||
/// Get deprecation message with migration guidance
|
||||
pub fn deprecation_notice(&self) -> Option<&str> {
|
||||
if self.deprecated && !self.deprecation_message.is_empty() {
|
||||
Some(&self.deprecation_message)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare tool versions. Returns true if self is newer than other.
|
||||
/// Uses simple semver comparison (major.minor.patch).
|
||||
pub fn is_newer_than(&self, other: &Self) -> bool {
|
||||
compare_versions(&self.version, &other.version) == std::cmp::Ordering::Greater
|
||||
}
|
||||
|
||||
/// Filter tools by execution context
|
||||
pub fn filter_by_context(tools: &[ToolDefinition], context: ExecutionContext) -> Vec<ToolDefinition> {
|
||||
match context {
|
||||
ExecutionContext::Both => tools.to_vec(),
|
||||
context => tools
|
||||
.iter()
|
||||
.filter(|t| t.execution_context == context || t.execution_context == ExecutionContext::Both)
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter tools to only include non-deprecated ones
|
||||
pub fn filter_active(tools: &[ToolDefinition]) -> Vec<ToolDefinition> {
|
||||
tools
|
||||
.iter()
|
||||
.filter(|t| !t.deprecated)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Check compatibility with another tool definition
|
||||
pub fn is_compatible_with(&self, other: &Self) -> bool {
|
||||
self.name == other.name && !self.deprecated && !other.deprecated
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple semver comparison (major.minor.patch)
|
||||
fn compare_versions(a: &str, b: &str) -> std::cmp::Ordering {
|
||||
let parse_version = |v: &str| -> Vec<u64> {
|
||||
v.split('.')
|
||||
.filter_map(|part| part.parse::<u64>().ok())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let v1 = parse_version(a);
|
||||
let v2 = parse_version(b);
|
||||
|
||||
for (p1, p2) in v1.iter().zip(v2.iter()) {
|
||||
match p1.cmp(p2) {
|
||||
std::cmp::Ordering::Equal => continue,
|
||||
non_eq => return non_eq,
|
||||
}
|
||||
}
|
||||
v1.len().cmp(&v2.len())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -301,6 +395,149 @@ pub struct ToolErrorInfo {
|
|||
pub message: String,
|
||||
}
|
||||
|
||||
/// Extended version tracking for tool definitions.
|
||||
/// Provides schema versioning, deprecation notices, and migration guidance.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolVersion {
|
||||
pub version: String,
|
||||
#[serde(default)]
|
||||
pub schema: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub deprecation_notes: Option<String>,
|
||||
#[serde(default)]
|
||||
pub migration_guide: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ToolVersion {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: "1.0.0".to_string(),
|
||||
schema: serde_json::Value::Null,
|
||||
deprecation_notes: None,
|
||||
migration_guide: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execution context for tool filtering.
|
||||
/// Determines which tools are available in which execution environment.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolContext {
|
||||
Reef,
|
||||
Krill,
|
||||
Both,
|
||||
}
|
||||
|
||||
impl Default for ToolContext {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
/// Tool category classification for UI and filtering.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolCategory {
|
||||
Filesystem,
|
||||
Execution,
|
||||
Git,
|
||||
Compilation,
|
||||
Network,
|
||||
Workspace,
|
||||
Kanban,
|
||||
Documentation,
|
||||
Strategic,
|
||||
Agent,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl ToolCategory {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Filesystem => "filesystem",
|
||||
Self::Execution => "execution",
|
||||
Self::Git => "git",
|
||||
Self::Compilation => "compilation",
|
||||
Self::Network => "network",
|
||||
Self::Workspace => "workspace",
|
||||
Self::Kanban => "kanban",
|
||||
Self::Documentation => "documentation",
|
||||
Self::Strategic => "strategic",
|
||||
Self::Agent => "agent",
|
||||
Self::Other => "other",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A composed workflow of multiple tool steps executed in sequence
|
||||
/// with dependency-based ordering.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolComposition {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
pub steps: Vec<CompositionStep>,
|
||||
#[serde(default = "default_composition_timeout_ms")]
|
||||
pub timeout_ms: u64,
|
||||
}
|
||||
|
||||
const fn default_composition_timeout_ms() -> u64 {
|
||||
60000
|
||||
}
|
||||
|
||||
/// A single step within a tool composition.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompositionStep {
|
||||
pub tool: String,
|
||||
pub arguments: serde_json::Value,
|
||||
/// Indices of steps this step depends on (0-based).
|
||||
/// If None, the step has no dependencies and can run immediately.
|
||||
#[serde(default)]
|
||||
pub depends_on: Option<Vec<usize>>,
|
||||
/// Optional label for referencing step outputs
|
||||
#[serde(default)]
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
/// Declares a dependency of one tool on another tool version.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDependency {
|
||||
pub tool: String,
|
||||
#[serde(default = "default_dependency_version")]
|
||||
pub version: String,
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
#[serde(default)]
|
||||
pub conflicts: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_dependency_version() -> String {
|
||||
"1.0.0".to_string()
|
||||
}
|
||||
|
||||
impl ToolDependency {
|
||||
pub fn new(tool: &str, version: &str) -> Self {
|
||||
Self {
|
||||
tool: tool.to_string(),
|
||||
version: version.to_string(),
|
||||
optional: false,
|
||||
conflicts: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optional(mut self) -> Self {
|
||||
self.optional = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_conflict(mut self, tool: &str) -> Self {
|
||||
self.conflicts.push(tool.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tool {
|
||||
($name:expr, $desc:expr, $cat:expr, $dangerous:expr, $approval:expr, $scoped:expr, $params:expr) => {
|
||||
ToolDefinition {
|
||||
|
|
@ -314,6 +551,9 @@ macro_rules! tool {
|
|||
version: "1.0.0".to_string(),
|
||||
project_scoped: $scoped,
|
||||
timeout_ms: default_timeout_ms(),
|
||||
deprecated: false,
|
||||
deprecation_message: String::new(),
|
||||
execution_context: ExecutionContext::default(),
|
||||
}
|
||||
};
|
||||
($name:expr, $desc:expr, $cat:expr, $dangerous:expr, $approval:expr, $scoped:expr, $params:expr, $timeout:expr) => {
|
||||
|
|
@ -328,10 +568,157 @@ macro_rules! tool {
|
|||
version: "1.0.0".to_string(),
|
||||
project_scoped: $scoped,
|
||||
timeout_ms: $timeout,
|
||||
deprecated: false,
|
||||
deprecation_message: String::new(),
|
||||
execution_context: ExecutionContext::default(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait for implementing custom tool plugins.
|
||||
/// Plugins can be registered at runtime to extend the tool ecosystem.
|
||||
pub trait ToolPlugin: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
fn description(&self) -> &str;
|
||||
fn category(&self) -> &str;
|
||||
fn schema(&self) -> serde_json::Value;
|
||||
fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, String>;
|
||||
}
|
||||
|
||||
/// A marketplace listing for a published tool.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolListing {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub author: String,
|
||||
#[serde(default)]
|
||||
pub author_id: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub category: String,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub downloads: u64,
|
||||
#[serde(default)]
|
||||
pub rating: f64,
|
||||
#[serde(default)]
|
||||
pub review_count: u64,
|
||||
#[serde(default)]
|
||||
pub source_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub schema: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub published_at: String,
|
||||
}
|
||||
|
||||
/// Results from a marketplace search.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MarketplaceSearchResults {
|
||||
pub listings: Vec<ToolListing>,
|
||||
pub total_count: usize,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
}
|
||||
|
||||
/// A documented example for a tool.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolExample {
|
||||
pub id: String,
|
||||
pub tool_name: String,
|
||||
pub title: String,
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
pub arguments: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub expected_output: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
#[serde(default)]
|
||||
pub author: String,
|
||||
}
|
||||
|
||||
/// A single test case for a tool.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolTest {
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub expected_output: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub expected_error: Option<String>,
|
||||
#[serde(default = "default_test_timeout")]
|
||||
pub timeout_ms: u64,
|
||||
}
|
||||
|
||||
const fn default_test_timeout() -> u64 {
|
||||
30000
|
||||
}
|
||||
|
||||
/// A suite of tests for a specific tool.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolTestSuite {
|
||||
pub tool_name: String,
|
||||
pub tests: Vec<ToolTest>,
|
||||
#[serde(default)]
|
||||
pub setup_actions: Vec<TestAction>,
|
||||
#[serde(default)]
|
||||
pub teardown_actions: Vec<TestAction>,
|
||||
}
|
||||
|
||||
/// A setup or teardown action for a test suite.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TestAction {
|
||||
pub tool: String,
|
||||
pub arguments: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Result of running a single tool test.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolTestResult {
|
||||
pub test_name: String,
|
||||
pub passed: bool,
|
||||
#[serde(default)]
|
||||
pub actual_output: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
pub duration_ms: u64,
|
||||
}
|
||||
|
||||
/// Results of running a full test suite.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolTestSuiteResult {
|
||||
pub tool_name: String,
|
||||
pub total: usize,
|
||||
pub passed: usize,
|
||||
pub failed: usize,
|
||||
pub results: Vec<ToolTestResult>,
|
||||
pub total_duration_ms: u64,
|
||||
}
|
||||
|
||||
/// Generated documentation for a tool.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDocumentation {
|
||||
pub tool_name: String,
|
||||
pub description: String,
|
||||
pub category: String,
|
||||
pub version: String,
|
||||
pub markdown: String,
|
||||
pub parameters: serde_json::Value,
|
||||
#[serde(default)]
|
||||
pub dangerous: bool,
|
||||
#[serde(default)]
|
||||
pub requires_approval: bool,
|
||||
#[serde(default)]
|
||||
pub deprecated: bool,
|
||||
#[serde(default)]
|
||||
pub deprecation_message: String,
|
||||
#[serde(default)]
|
||||
pub examples: Vec<ToolExample>,
|
||||
}
|
||||
|
||||
/// Returns all built-in tool definitions across all categories.
|
||||
pub fn tool_definitions() -> Vec<ToolDefinition> {
|
||||
vec![
|
||||
|
|
@ -1485,4 +1872,148 @@ Done with tools.
|
|||
assert!(matches!(deserialized.status, ToolResultStatus::Success));
|
||||
assert_eq!(deserialized.data.unwrap()["result"], "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_definition_is_deprecated() {
|
||||
let mut def = ToolDefinition::default();
|
||||
assert!(!def.is_deprecated());
|
||||
|
||||
def.deprecated = true;
|
||||
assert!(def.is_deprecated());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_definition_deprecation_notice() {
|
||||
let mut def = ToolDefinition::default();
|
||||
assert!(def.deprecation_notice().is_none());
|
||||
|
||||
def.deprecated = true;
|
||||
assert!(def.deprecation_notice().is_none());
|
||||
|
||||
def.deprecation_message = "Use new_tool instead".to_string();
|
||||
assert_eq!(def.deprecation_notice(), Some("Use new_tool instead"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_version_comparison() {
|
||||
let old = ToolDefinition {
|
||||
version: "1.0.0".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let new = ToolDefinition {
|
||||
version: "2.0.0".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
assert!(new.is_newer_than(&old));
|
||||
assert!(!old.is_newer_than(&new));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_by_context() {
|
||||
let reef_tool = ToolDefinition {
|
||||
name: "reef_tool".to_string(),
|
||||
execution_context: ExecutionContext::Reef,
|
||||
..Default::default()
|
||||
};
|
||||
let krill_tool = ToolDefinition {
|
||||
name: "krill_tool".to_string(),
|
||||
execution_context: ExecutionContext::Krill,
|
||||
..Default::default()
|
||||
};
|
||||
let both_tool = ToolDefinition {
|
||||
name: "both_tool".to_string(),
|
||||
execution_context: ExecutionContext::Both,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let tools = vec![reef_tool, krill_tool, both_tool];
|
||||
|
||||
let reef_tools = ToolDefinition::filter_by_context(&tools, ExecutionContext::Reef);
|
||||
assert_eq!(reef_tools.len(), 2);
|
||||
assert!(reef_tools.iter().any(|t| t.name == "reef_tool"));
|
||||
assert!(reef_tools.iter().any(|t| t.name == "both_tool"));
|
||||
|
||||
let krill_tools = ToolDefinition::filter_by_context(&tools, ExecutionContext::Krill);
|
||||
assert_eq!(krill_tools.len(), 2);
|
||||
assert!(krill_tools.iter().any(|t| t.name == "krill_tool"));
|
||||
assert!(krill_tools.iter().any(|t| t.name == "both_tool"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_active_excludes_deprecated() {
|
||||
let active = ToolDefinition {
|
||||
name: "active".to_string(),
|
||||
deprecated: false,
|
||||
..Default::default()
|
||||
};
|
||||
let deprecated = ToolDefinition {
|
||||
name: "deprecated".to_string(),
|
||||
deprecated: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let tools = vec![active, deprecated];
|
||||
let active_tools = ToolDefinition::filter_active(&tools);
|
||||
assert_eq!(active_tools.len(), 1);
|
||||
assert_eq!(active_tools[0].name, "active");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_compatibility() {
|
||||
let tool_a = ToolDefinition {
|
||||
name: "my_tool".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let tool_b = ToolDefinition {
|
||||
name: "my_tool".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let tool_c = ToolDefinition {
|
||||
name: "other_tool".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(tool_a.is_compatible_with(&tool_b));
|
||||
assert!(!tool_a.is_compatible_with(&tool_c));
|
||||
|
||||
let mut deprecated_tool = ToolDefinition {
|
||||
name: "my_tool".to_string(),
|
||||
deprecated: true,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(!tool_a.is_compatible_with(&deprecated_tool));
|
||||
deprecated_tool.deprecated = false;
|
||||
assert!(tool_a.is_compatible_with(&deprecated_tool));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toolversion_default() {
|
||||
let version = ToolVersion::default();
|
||||
assert_eq!(version.version, "1.0.0");
|
||||
assert!(version.deprecation_notes.is_none());
|
||||
assert!(version.migration_guide.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execution_context_default() {
|
||||
assert_eq!(
|
||||
ExecutionContext::default(),
|
||||
ExecutionContext::Both
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toolcategory_as_str() {
|
||||
assert_eq!(ToolCategory::Filesystem.as_str(), "filesystem");
|
||||
assert_eq!(ToolCategory::Execution.as_str(), "execution");
|
||||
assert_eq!(ToolCategory::Git.as_str(), "git");
|
||||
assert_eq!(ToolCategory::Compilation.as_str(), "compilation");
|
||||
assert_eq!(ToolCategory::Network.as_str(), "network");
|
||||
assert_eq!(ToolCategory::Workspace.as_str(), "workspace");
|
||||
assert_eq!(ToolCategory::Kanban.as_str(), "kanban");
|
||||
assert_eq!(ToolCategory::Documentation.as_str(), "documentation");
|
||||
assert_eq!(ToolCategory::Strategic.as_str(), "strategic");
|
||||
assert_eq!(ToolCategory::Agent.as_str(), "agent");
|
||||
assert_eq!(ToolCategory::Other.as_str(), "other");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue