Skip to main content

systemprompt_wire/
error.rs

1//! Failures raised while reading a buffered upstream body or an upstream SSE
2//! stream.
3//!
4//! The per-wire buffered parsers are lenient about *missing optional fields*
5//! and strict about the body's overall shape: a reply whose top-level
6//! deserialization fails is an upstream failure, never an empty success, so it
7//! reaches the client as an error and the audit row as a failed request.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use thiserror::Error;
13
14/// Failure to read a buffered upstream body into a canonical response.
15///
16/// Each variant names the wire dialect whose contract the body broke, so the
17/// relayed message identifies the adapter without the caller adding context.
18#[derive(Debug, Error)]
19pub enum WireParseError {
20    #[error("Malformed Anthropic response body: {0}")]
21    Anthropic(#[source] serde_json::Error),
22    #[error("Malformed Gemini response body: {0}")]
23    Gemini(#[source] serde_json::Error),
24    #[error("Malformed OpenAI chat completion body: {0}")]
25    OpenAiChat(#[source] serde_json::Error),
26    #[error("Malformed OpenAI responses body: {0}")]
27    OpenAiResponses(#[source] serde_json::Error),
28    // Why: the Responses API rejects a replayed function call whose id is
29    // missing, so a call that arrives without `call_id` and `id` cannot be
30    // represented as a canonical tool use.
31    #[error("OpenAI responses body: function_call `{name}` carries neither call_id nor id")]
32    OpenAiResponsesMissingToolCallId { name: String },
33    // Why: a well-formed body whose only candidate finished on a refusal or an
34    // unclassified reason without a single part is the provider ending the
35    // turn, and it must reach the client as an error rather than an empty
36    // success — the message names the raw reason and whatever the provider
37    // said about it.
38    #[error("{0}")]
39    EmptyTerminal(String),
40}
41
42/// Failure of an upstream SSE stream while translating it to canonical events.
43#[derive(Debug, Error)]
44pub enum WireStreamError {
45    #[error("{0}")]
46    Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
47    #[error("malformed Gemini SSE frame: {0}")]
48    MalformedGeminiFrame(#[source] serde_json::Error),
49    #[error("malformed Responses SSE frame: {0}")]
50    MalformedResponsesFrame(#[source] serde_json::Error),
51}
52
53impl WireStreamError {
54    pub fn transport(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
55        Self::Transport(source.into())
56    }
57}