1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
13pub enum ToolErrorCode {
14 NotFound,
15 Binary,
16 TooLarge,
17 OutsideWorkspace,
18 Sensitive,
19 PermissionDenied,
20 InvalidParam,
21 IoError,
22 NotReadThisSession,
23 StaleRead,
24 OldStringNotFound,
25 OldStringNotUnique,
26 EmptyFile,
27 NoOpEdit,
28 BinaryNotEditable,
29 NotebookUnsupported,
30 DeniedByHook,
31 ValidateFailed,
32 InvalidRegex,
33 Timeout,
34 Killed,
35 InvalidUrl,
36 SsrfBlocked,
37 DnsError,
38 TlsError,
39 ConnectionReset,
40 Oversize,
41 UnsupportedContentType,
42 RedirectLoop,
43 InteractiveDetected,
44 ServerNotAvailable,
45 ServerCrashed,
46 PositionInvalid,
47 InvalidFrontmatter,
48 NameMismatch,
49 Disabled,
50 NotTrusted,
51}
52
53impl ToolErrorCode {
54 pub fn as_str(&self) -> &'static str {
56 match self {
60 Self::NotFound => "NOT_FOUND",
61 Self::Binary => "BINARY",
62 Self::TooLarge => "TOO_LARGE",
63 Self::OutsideWorkspace => "OUTSIDE_WORKSPACE",
64 Self::Sensitive => "SENSITIVE",
65 Self::PermissionDenied => "PERMISSION_DENIED",
66 Self::InvalidParam => "INVALID_PARAM",
67 Self::IoError => "IO_ERROR",
68 Self::NotReadThisSession => "NOT_READ_THIS_SESSION",
69 Self::StaleRead => "STALE_READ",
70 Self::OldStringNotFound => "OLD_STRING_NOT_FOUND",
71 Self::OldStringNotUnique => "OLD_STRING_NOT_UNIQUE",
72 Self::EmptyFile => "EMPTY_FILE",
73 Self::NoOpEdit => "NO_OP_EDIT",
74 Self::BinaryNotEditable => "BINARY_NOT_EDITABLE",
75 Self::NotebookUnsupported => "NOTEBOOK_UNSUPPORTED",
76 Self::DeniedByHook => "DENIED_BY_HOOK",
77 Self::ValidateFailed => "VALIDATE_FAILED",
78 Self::InvalidRegex => "INVALID_REGEX",
79 Self::Timeout => "TIMEOUT",
80 Self::Killed => "KILLED",
81 Self::InvalidUrl => "INVALID_URL",
82 Self::SsrfBlocked => "SSRF_BLOCKED",
83 Self::DnsError => "DNS_ERROR",
84 Self::TlsError => "TLS_ERROR",
85 Self::ConnectionReset => "CONNECTION_RESET",
86 Self::Oversize => "OVERSIZE",
87 Self::UnsupportedContentType => "UNSUPPORTED_CONTENT_TYPE",
88 Self::RedirectLoop => "REDIRECT_LOOP",
89 Self::InteractiveDetected => "INTERACTIVE_DETECTED",
90 Self::ServerNotAvailable => "SERVER_NOT_AVAILABLE",
91 Self::ServerCrashed => "SERVER_CRASHED",
92 Self::PositionInvalid => "POSITION_INVALID",
93 Self::InvalidFrontmatter => "INVALID_FRONTMATTER",
94 Self::NameMismatch => "NAME_MISMATCH",
95 Self::Disabled => "DISABLED",
96 Self::NotTrusted => "NOT_TRUSTED",
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ToolError {
109 pub code: ToolErrorCode,
110 pub message: String,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub cause: Option<Value>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub meta: Option<Value>,
115}
116
117impl ToolError {
118 pub fn new(code: ToolErrorCode, message: impl Into<String>) -> Self {
119 Self {
120 code,
121 message: message.into(),
122 cause: None,
123 meta: None,
124 }
125 }
126
127 pub fn with_meta(mut self, meta: Value) -> Self {
128 self.meta = Some(meta);
129 self
130 }
131
132 pub fn with_cause(mut self, cause: Value) -> Self {
133 self.cause = Some(cause);
134 self
135 }
136}
137
138impl std::fmt::Display for ToolError {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 write!(f, "{}", format_tool_error(self))
141 }
142}
143
144impl std::error::Error for ToolError {}
145
146pub fn format_tool_error(err: &ToolError) -> String {
149 format!("Error [{}]: {}", err.code.as_str(), err.message)
150}