Skip to main content

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    /// A workbench action was refused: the session, job, conversation, run,
53    /// approval, or worktree it names is unknown, or cannot do that now.
54    WorkbenchError,
55    /// The model provider failed the turn.
56    ProviderError,
57    /// The turn's history does not fit the model's context window.
58    ContextLimit,
59    /// The turn reached `agent.max_steps`.
60    StepLimit,
61    /// The turn alone exceeds the session's history limits.
62    HistoryLimit,
63    /// A response exceeded a size limit.
64    ResponseLimit,
65    /// A response asked for too many tool calls, or too large arguments.
66    ToolLimit,
67    /// A server invariant failed.
68    InternalError,
69    /// A code this client does not know, from a newer server.
70    #[serde(other)]
71    Unknown,
72}
73
74impl ErrorCode {
75    /// The code as it appears on the wire, such as `queue_limit`.
76    pub fn as_str(self) -> &'static str {
77        match self {
78            Self::InvalidJson => "invalid_json",
79            Self::NotInitialized => "not_initialized",
80            Self::VersionMismatch => "version_mismatch",
81            Self::InvalidRequest => "invalid_request",
82            Self::Unsupported => "unsupported",
83            Self::SessionNotFound => "session_not_found",
84            Self::TurnActive => "turn_active",
85            Self::TurnNotFound => "turn_not_found",
86            Self::ApprovalNotFound => "approval_not_found",
87            Self::QueueLimit => "queue_limit",
88            Self::QueueNotFound => "queue_not_found",
89            Self::QueueConflict => "queue_conflict",
90            Self::ComponentError => "component_error",
91            Self::DelegationError => "delegation_error",
92            Self::RestartError => "restart_error",
93            Self::ConfirmError => "confirm_error",
94            Self::WorkbenchError => "workbench_error",
95            Self::ProviderError => "provider_error",
96            Self::ContextLimit => "context_limit",
97            Self::StepLimit => "step_limit",
98            Self::HistoryLimit => "history_limit",
99            Self::ResponseLimit => "response_limit",
100            Self::ToolLimit => "tool_limit",
101            Self::InternalError => "internal_error",
102            Self::Unknown => "unknown",
103        }
104    }
105}
106
107impl fmt::Display for ErrorCode {
108    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109        formatter.write_str(self.as_str())
110    }
111}
112
113/// Why a tool call failed (`tool.completed.error`). The model reads the
114/// call's output either way; this tells a client how to show it.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117#[non_exhaustive]
118pub enum ToolErrorKind {
119    /// The approval policy or the user refused the call.
120    Denied,
121    /// The call was cancelled while it ran.
122    Cancelled,
123    /// The call's arguments were refused before it ran.
124    InvalidArguments,
125    /// The tool, or the agent it runs, could not be used: missing, signed
126    /// out, or its provider unreachable.
127    Unavailable,
128    /// A configured size, count, depth, or time limit stopped the call.
129    Limit,
130    /// The call ran and failed.
131    Failed,
132    /// The model named a tool the session does not have.
133    UnknownTool,
134    /// A kind this client does not know, from a newer server.
135    #[serde(other)]
136    Unknown,
137}
138
139impl ToolErrorKind {
140    /// The kind as it appears on the wire, such as `denied`.
141    pub(crate) fn as_str(self) -> &'static str {
142        match self {
143            Self::Denied => "denied",
144            Self::Cancelled => "cancelled",
145            Self::InvalidArguments => "invalid_arguments",
146            Self::Unavailable => "unavailable",
147            Self::Limit => "limit",
148            Self::Failed => "failed",
149            Self::UnknownTool => "unknown_tool",
150            Self::Unknown => "unknown",
151        }
152    }
153}
154
155impl fmt::Display for ToolErrorKind {
156    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157        formatter.write_str(self.as_str())
158    }
159}