openpencil/crates/copilot-sdk/src/error.rs
Kayshen-X f3f57081da chore(crates): move vendored SDKs into workspace as forkable crates
Per user direction "可以不放在 vendor 里面,我们移动到自己的工程,
后面就和他们分叉" — promote the two community SDKs from vendor/ to
crates/ so they become first-class OP workspace members we own and
evolve, instead of read-only vendored snapshots.

Moves:
  vendor/anthropic-agent-sdk/  →  crates/anthropic-agent-sdk/
  vendor/copilot-sdk-rust/     →  crates/copilot-sdk/

Workspace integration:
  - Root `Cargo.toml` exclude list drops both vendor entries; the
    existing `members = ["crates/*"]` glob auto-includes them.
  - `crates/copilot-sdk/Cargo.toml`: stripped all `[[example]]`
    blocks (22 of them) — the examples/ dir was already removed
    during the import, and leaving the entries broke
    `cargo test --workspace --no-run`.
  - `crates/anthropic-agent-sdk/Cargo.toml`: already had its
    `[[example]]` blocks pruned in the previous commit.

Lockfile pins (workspace `Cargo.lock`):
  Pulling reqwest 0.12.28 (via anthropic-agent-sdk) into the
  unified workspace dep graph re-resolved several `icu_*` crates to
  the 2.2 line, which requires rustc 1.86. OP's toolchain is 1.85
  (locked to stay compatible with the skia-safe-op fork). Pinned:
    icu_collections      2.2.0 → 2.1.1
    icu_locale_core      2.2.0 → 2.1.1
    icu_normalizer       2.2.0 → 2.1.1
    icu_normalizer_data  2.2.0 → 2.1.1
    icu_properties       2.2.0 → 2.1.2
    icu_properties_data  2.2.0 → 2.1.2
    icu_provider         2.2.0 → 2.1.1
    idna_adapter         1.2.2 → 1.2.1
  All eight pins are the latest versions on each crate's 2.1.x /
  1.2.x line that compile on rustc 1.85.

Verification:
  - `cargo check -p anthropic-agent-sdk` ✓
  - `cargo check -p copilot-sdk` ✓
  - `cargo test --workspace --no-run` ✓
  - `cargo test -p openpencil-shell-core --lib` → 250 pass
  - `cargo test -p openpencil-desktop chat_` → 16 pass

Next: replace the hand-rolled subprocess parser in chat_subprocess.rs
with thin per-CLI adapters that route Claude Code through
`anthropic_agent_sdk::SubprocessTransport` and Copilot through
`copilot_sdk::Client + Session`. Gemini stays on the generic stdin
bridge until an upstream Rust SDK exists. Codex + OpenCode get an
HttpServerProvider that spawns `<bin> serve` then connects via a
local HTTP client.
2026-05-14 16:49:17 +08:00

158 lines
4.2 KiB
Rust

// Copyright (c) 2026 Elias Bachaalany
// SPDX-License-Identifier: MIT
//! Error types for the Copilot SDK.
use std::time::Duration;
use thiserror::Error;
/// Main error type for the Copilot SDK.
#[derive(Debug, Error)]
pub enum CopilotError {
/// Transport/IO error
#[error("Transport error: {0}")]
Transport(#[from] std::io::Error),
/// Connection was closed unexpectedly
#[error("Connection closed")]
ConnectionClosed,
/// Client is not connected
#[error("Not connected")]
NotConnected,
/// JSON-RPC error from server
#[error("JSON-RPC error {code}: {message}")]
JsonRpc {
code: i32,
message: String,
data: Option<serde_json::Value>,
},
/// Protocol version mismatch
#[error("Protocol version mismatch: SDK supports versions {min}-{max}, but server reports version {actual}. Please update your SDK or server to ensure compatibility.")]
ProtocolMismatch { min: u32, max: u32, actual: u32 },
/// Protocol error (framing, invalid messages, etc.)
#[error("Protocol error: {0}")]
Protocol(String),
/// JSON serialization/deserialization error
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
/// Request timed out
#[error("Request timed out after {0:?}")]
Timeout(Duration),
/// Session not found
#[error("Session not found: {0}")]
SessionNotFound(String),
/// Session was already destroyed
#[error("Session already destroyed")]
SessionDestroyed,
/// Invalid configuration
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
/// Failed to start CLI process
#[error("Failed to start CLI: {0}")]
ProcessStart(std::io::Error),
/// CLI process exited unexpectedly
#[error("CLI exited unexpectedly with code {0:?}")]
ProcessExit(Option<i32>),
/// Port detection failed in TCP mode
#[error("Failed to detect CLI server port")]
PortDetectionFailed,
/// Client is shutting down
#[error("Client is shutting down")]
Shutdown,
/// Tool not found
#[error("Tool not found: {0}")]
ToolNotFound(String),
/// Tool execution error
#[error("Tool execution error: {0}")]
ToolError(String),
/// Permission denied
#[error("Permission denied: {0}")]
PermissionDenied(String),
/// Channel send error (internal)
#[error("Internal channel error")]
ChannelError,
}
/// Result type alias for Copilot SDK operations.
pub type Result<T> = std::result::Result<T, CopilotError>;
impl CopilotError {
/// Create a JSON-RPC error from components.
pub fn json_rpc(
code: i32,
message: impl Into<String>,
data: Option<serde_json::Value>,
) -> Self {
Self::JsonRpc {
code,
message: message.into(),
data,
}
}
/// Create an invalid config error.
pub fn invalid_config(msg: impl Into<String>) -> Self {
Self::InvalidConfig(msg.into())
}
/// Returns true if this error indicates the connection is no longer usable.
pub fn is_fatal(&self) -> bool {
matches!(
self,
CopilotError::ConnectionClosed
| CopilotError::ProcessExit(_)
| CopilotError::Shutdown
| CopilotError::ProtocolMismatch { .. }
)
}
}
// Convert oneshot channel errors to our error type
impl From<tokio::sync::oneshot::error::RecvError> for CopilotError {
fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
CopilotError::ConnectionClosed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = CopilotError::ProtocolMismatch {
min: 2,
max: 3,
actual: 1,
};
assert_eq!(
err.to_string(),
"Protocol version mismatch: SDK supports versions 2-3, but server reports version 1. Please update your SDK or server to ensure compatibility."
);
}
#[test]
fn test_is_fatal() {
assert!(CopilotError::ConnectionClosed.is_fatal());
assert!(CopilotError::Shutdown.is_fatal());
assert!(!CopilotError::Timeout(Duration::from_secs(30)).is_fatal());
}
}