scv_protocol/error.rs
1//! Stable error codes: why a request or turn failed ([`ErrorCode`]), and why
2//! a tool call did ([`ToolErrorKind`]).
3//!
4//! Both are closed lists on the wire, but a newer server may add a value. An
5//! unrecognized string parses as `Unknown` rather than failing the whole
6//! event, so a client keeps working and shows the event's message.
7
8use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12/// Why a request (`error`) or a turn (`turn.failed`) failed.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15#[non_exhaustive]
16pub enum ErrorCode {
17 /// The frame is not JSON, or not a message this server knows, such as a
18 /// `daemon.control` action it predates.
19 InvalidJson,
20 /// A message other than `initialize` came first.
21 NotInitialized,
22 /// The client speaks another protocol version; the connection closes.
23 VersionMismatch,
24 /// The message is well-formed but its values are refused.
25 InvalidRequest,
26 /// This endpoint does not offer the request, such as component
27 /// management on stdio.
28 Unsupported,
29 /// No session, or a different one, is running on this connection.
30 SessionNotFound,
31 /// The session must be idle for this request.
32 TurnActive,
33 /// The turn named is not running.
34 TurnNotFound,
35 /// The approval is unknown or already resolved.
36 ApprovalNotFound,
37 /// The session's queue is full.
38 QueueLimit,
39 /// The queued prompt is gone.
40 QueueNotFound,
41 /// The queued prompt's revision is stale.
42 QueueConflict,
43 /// A channel account could not be changed.
44 ComponentError,
45 /// A delegation control request named nothing, or an unknown handle.
46 DelegationError,
47 /// The daemon refused to schedule a restart.
48 RestartError,
49 /// The daemon could not ask the owner a question (no owner chat to ask
50 /// in, or one already waiting there), or does not know the one named.
51 ConfirmError,
52 /// The model provider failed the turn.
53 ProviderError,
54 /// The turn's history does not fit the model's context window.
55 ContextLimit,
56 /// The turn reached `agent.max_steps`.
57 StepLimit,
58 /// The turn alone exceeds the session's history limits.
59 HistoryLimit,
60 /// A response exceeded a size limit.
61 ResponseLimit,
62 /// A response asked for too many tool calls, or too large arguments.
63 ToolLimit,
64 /// A server invariant failed.
65 InternalError,
66 /// A code this client does not know, from a newer server.
67 #[serde(other)]
68 Unknown,
69}
70
71impl ErrorCode {
72 /// The code as it appears on the wire, such as `queue_limit`.
73 pub fn as_str(self) -> &'static str {
74 match self {
75 Self::InvalidJson => "invalid_json",
76 Self::NotInitialized => "not_initialized",
77 Self::VersionMismatch => "version_mismatch",
78 Self::InvalidRequest => "invalid_request",
79 Self::Unsupported => "unsupported",
80 Self::SessionNotFound => "session_not_found",
81 Self::TurnActive => "turn_active",
82 Self::TurnNotFound => "turn_not_found",
83 Self::ApprovalNotFound => "approval_not_found",
84 Self::QueueLimit => "queue_limit",
85 Self::QueueNotFound => "queue_not_found",
86 Self::QueueConflict => "queue_conflict",
87 Self::ComponentError => "component_error",
88 Self::DelegationError => "delegation_error",
89 Self::RestartError => "restart_error",
90 Self::ConfirmError => "confirm_error",
91 Self::ProviderError => "provider_error",
92 Self::ContextLimit => "context_limit",
93 Self::StepLimit => "step_limit",
94 Self::HistoryLimit => "history_limit",
95 Self::ResponseLimit => "response_limit",
96 Self::ToolLimit => "tool_limit",
97 Self::InternalError => "internal_error",
98 Self::Unknown => "unknown",
99 }
100 }
101}
102
103impl fmt::Display for ErrorCode {
104 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105 formatter.write_str(self.as_str())
106 }
107}
108
109/// Why a tool call failed (`tool.completed.error`). The model reads the
110/// call's output either way; this tells a client how to show it.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113#[non_exhaustive]
114pub enum ToolErrorKind {
115 /// The approval policy or the user refused the call.
116 Denied,
117 /// The call was cancelled while it ran.
118 Cancelled,
119 /// The call's arguments were refused before it ran.
120 InvalidArguments,
121 /// The tool, or the agent it runs, could not be used: missing, signed
122 /// out, or its provider unreachable.
123 Unavailable,
124 /// A configured size, count, depth, or time limit stopped the call.
125 Limit,
126 /// The call ran and failed.
127 Failed,
128 /// The model named a tool the session does not have.
129 UnknownTool,
130 /// A kind this client does not know, from a newer server.
131 #[serde(other)]
132 Unknown,
133}
134
135impl ToolErrorKind {
136 /// The kind as it appears on the wire, such as `denied`.
137 pub(crate) fn as_str(self) -> &'static str {
138 match self {
139 Self::Denied => "denied",
140 Self::Cancelled => "cancelled",
141 Self::InvalidArguments => "invalid_arguments",
142 Self::Unavailable => "unavailable",
143 Self::Limit => "limit",
144 Self::Failed => "failed",
145 Self::UnknownTool => "unknown_tool",
146 Self::Unknown => "unknown",
147 }
148 }
149}
150
151impl fmt::Display for ToolErrorKind {
152 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
153 formatter.write_str(self.as_str())
154 }
155}