SDK Alignment and Compatibility
SDK Alignment and Compatibility
Section titled “SDK Alignment and Compatibility”SDK alignment
Section titled “SDK alignment”The Anthropic Messages wire model is compared against the vendored official TypeScript SDK under contrib/anthropic-sdk-typescript using:
just compare-anthropic-protocolThe comparison checks type coverage, field coverage and order, serde discriminator handling, enum literals, untagged unions, field carriers, and structured SDK markers. Every public wire type must also declare explicit provenance in its doc comments:
/// @sdk(shape = "SearchResultBlockParam")pub struct SearchResultBlockParam { /* ... */ }
/// @sdk(alias = "ToolBash20250124")pub struct ServerToolDef { /* ... */ }
/// @sdk(proxai_internal = "field_literal_wrapper")pub enum ImageMediaType { /* ... */ }shape binds a local type to a named SDK export; alias records a deliberate many-to-one model; proxai_internal classifies a carrier for an inline SDK shape, union, discriminator, or field literal. The compare rejects an otherwise matching Rust name with no explicit provenance marker.
Presence and nullability
Section titled “Presence and nullability”Official schemas distinguish four field contracts:
| Official shape | Rust carrier |
|---|---|
| `field: T` | T |
| `field?: T` | Option<T> |
| `field: T | null` | RequiredNullable<T> |
| `field?: T | null` | OptionalNullable<T> |
RequiredNullable<T> is self-contained. Its JSON deserializer rejects a missing field, accepts explicit null as Nullable::null(), and delegates non-null values to T while preserving useful inner errors such as unknown enum variants. It needs no field-level serde attribute:
pub struct Usage { pub output_tokens: u32, pub server_tool_use: RequiredNullable<ServerToolUsage>,}Optional fields need field-level presence handling only when they use Rust’s outer Option<T>: serde otherwise collapses a missing field and explicit null. Optional non-nullable fields use:
#[serde( default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_present")]pub field: Option<T>;OptionalNullable<T> is a self-contained three-state carrier: Missing, Null, or Value(T). Its deserializer handles a present null or value, while #[serde(default)] supplies Missing for an omitted field:
#[serde(default, skip_serializing_if = "OptionalNullable::is_missing")]pub field: OptionalNullable<T>;The shared protocol compare rules enforce these named carriers and their distinct serde contracts for both OpenAI and Anthropic. They reject raw Option<Nullable<T>>, required-nullable fields modeled as Option<T>, and obsolete field-level deserializers for both nullable carriers.
Compatibility normalization
Section titled “Compatibility normalization”Provider compatibility normalization should repair only conservative or measured upstream deviations into the nearest official protocol shape. Current conservative repairs are SDK required-nullable response fields missing from JSON objects (missing -> null) and bare message_start events normalized into the official nested message shape. Current measured provider repairs:
- MiniMax-compatible Anthropic streams may omit
signatureon a thinkingcontent_block_start, so ProxAI inserts an empty signature for that narrow case. - MiniMax-compatible Chat Completions streams may omit required-nullable
choices[].finish_reasonbefore their terminal chunk, so ProxAI insertsnullfor the missing field. - GLM 5.1 Anthropic-compatible streams may emit
server_tool_usewith only one counter, so ProxAI fills the absentweb_fetch_requestsorweb_search_requestscounter with0.
Do not add other provider-specific business defaults, such as missing tool callers, unless a measured upstream case and a focused fixture document the behavior.
Keep these repairs local to provider compatibility handling. They should not redefine the official wire model.