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}