entelix_core/ir/warning.rs
1//! `ModelWarning` — non-fatal codec advisories (invariant 6).
2//!
3//! When a codec drops or coerces information that the IR carried, it must
4//! emit a `LossyEncode` here. Silent loss is an invariant violation.
5
6use serde::{Deserialize, Serialize};
7
8/// One non-fatal advisory from a codec or transport. Carried in
9/// `ModelResponse::warnings` and surfaced via observability.
10#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "kind", rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum ModelWarning {
14 /// The codec could not preserve every IR field on the wire. The vendor
15 /// will not see (or will see a coerced version of) the named field.
16 LossyEncode {
17 /// IR field that was dropped or coerced (e.g. `messages[2].content[0]`).
18 field: String,
19 /// Why the loss happened.
20 detail: String,
21 },
22 /// The provider returned a stop reason this codec does not yet model;
23 /// the IR carries it as `StopReason::Other`.
24 UnknownStopReason {
25 /// Raw vendor reason string.
26 raw: String,
27 },
28 /// The IR requested a feature the model doesn't support (e.g. tools on a
29 /// vision-only model). The codec proceeded with a degraded request.
30 UnsupportedCapability {
31 /// Capability name (e.g. `streaming`, `prompt_caching`).
32 capability: String,
33 /// Detail of what fallback was applied.
34 detail: String,
35 },
36 /// The IR carries a [`ProviderExtensions`](crate::ir::ProviderExtensions)
37 /// entry for a vendor different from the active codec — the
38 /// codec ignored the foreign knobs because the wire format
39 /// cannot express them. Operators that route the same request
40 /// across multiple codecs see one of these warnings per inactive
41 /// vendor that had ext set.
42 ProviderExtensionIgnored {
43 /// Vendor identifier matching the inactive ext field name
44 /// (`anthropic`, `openai_chat`, `openai_responses`, `gemini`,
45 /// `bedrock`).
46 vendor: String,
47 },
48}