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