use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ToolId(pub String);
impl ToolId {
pub fn new(name: &str) -> Self {
Self(name.to_string())
}
}
impl fmt::Display for ToolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for ToolId {
fn from(s: &str) -> Self {
Self::new(s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolRequest {
pub call_id: String,
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResponse {
pub call_id: String,
pub name: String,
pub content: String,
pub is_error: bool,
pub duration_ms: u64,
}
impl ToolResponse {
pub fn success(
call_id: impl Into<String>,
name: impl Into<String>,
content: impl Into<String>,
) -> Self {
Self {
call_id: call_id.into(),
name: name.into(),
content: content.into(),
is_error: false,
duration_ms: 0,
}
}
pub fn error(
call_id: impl Into<String>,
name: impl Into<String>,
error: impl Into<String>,
) -> Self {
Self {
call_id: call_id.into(),
name: name.into(),
content: error.into(),
is_error: true,
duration_ms: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolMeta {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
#[serde(default)]
pub requires_confirmation: bool,
#[serde(default)]
pub is_dangerous: bool,
pub category: ToolCategory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolCategory {
FileOps,
Search,
Shell,
Network,
CodeAnalysis,
Memory,
Workflow,
System,
DataProcessing,
TextProcessing,
VersionControl,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MemoryTier {
#[default]
Working,
Session,
Project,
LongTerm,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub tier: MemoryTier,
pub content: String,
#[serde(default)]
pub metadata: serde_json::Map<String, serde_json::Value>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub last_accessed: chrono::DateTime<chrono::Utc>,
#[serde(default)]
pub access_count: u32,
#[serde(default)]
pub importance: f32,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryQuery {
pub query: String,
pub tier: Option<MemoryTier>,
pub limit: Option<usize>,
pub time_range: Option<(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>)>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QueryType {
Definition,
References,
Implementations,
TypeDefinition,
DocumentSymbols,
WorkspaceSymbols,
Hover,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeLocation {
pub file: PathBuf,
pub line: u32,
pub column: u32,
}
impl fmt::Display for CodeLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.file.display(), self.line, self.column)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeRange {
pub start: CodeLocation,
pub end: CodeLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResult {
pub query_type: QueryType,
pub location: CodeLocation,
pub range: Option<CodeRange>,
pub display_text: String,
pub snippet: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProcessState {
Running,
Stopped,
Exited,
Killed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessInfo {
pub pid: u32,
pub name: String,
pub state: ProcessState,
pub started_at: chrono::DateTime<chrono::Utc>,
pub command: String,
pub working_dir: PathBuf,
}
#[derive(Debug, thiserror::Error)]
pub enum Layer3Error {
#[error("Tool not found: {0}")]
ToolNotFound(String),
#[error("Tool execution failed: {0}")]
ToolExecutionFailed(String),
#[error("Tool validation failed: {0}")]
ToolValidationFailed(String),
#[error("Memory not found: {0}")]
MemoryNotFound(String),
#[error("Memory query failed: {0}")]
MemoryQueryFailed(String),
#[error("Query failed: {0}")]
QueryFailed(String),
#[error("Process error: {0}")]
ProcessError(String),
#[error("LSP error: {0}")]
LspError(String),
#[error("Sandbox error: {0}")]
SandboxError(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Lock error: {0}")]
LockError(String),
#[error("Vector store error: {0}")]
VectorStoreError(String),
#[error("Vector dimension mismatch: expected {expected}, got {actual}")]
VectorDimensionMismatch { expected: usize, actual: usize },
#[error("Vector not found: {0}")]
VectorNotFound(String),
#[error("Vector operation failed: {operation} - {reason}")]
VectorOperationFailed { operation: String, reason: String },
#[error("Persistence error: {0}")]
PersistenceError(String),
#[error("Invalid vector: {0}")]
InvalidVector(String),
#[error("Index error: {0}")]
IndexError(String),
#[error("Configuration error: {0}")]
ConfigError(String),
}
pub type Layer3Result<T> = anyhow::Result<T>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_response_creation() {
let resp = ToolResponse::success("call_1", "test_tool", "result");
assert!(!resp.is_error);
assert_eq!(resp.name, "test_tool");
let err_resp = ToolResponse::error("call_2", "test_tool", "error");
assert!(err_resp.is_error);
}
#[test]
fn test_memory_tier_default() {
let tier = MemoryTier::default();
assert_eq!(tier, MemoryTier::Working);
}
#[test]
fn test_code_location_display() {
let loc = CodeLocation {
file: PathBuf::from("src/main.rs"),
line: 10,
column: 5,
};
assert!(loc.to_string().contains("main.rs"));
}
}