Skip to content

SDK Alignment and Compatibility

The Anthropic Messages wire model is compared against the vendored official TypeScript SDK under contrib/anthropic-sdk-typescript using:

Terminal window
just compare-anthropic-protocol

The 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.

Official schemas distinguish four field contracts:

Official shapeRust 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.

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 signature on a thinking content_block_start, so ProxAI inserts an empty signature for that narrow case.
  • MiniMax-compatible Chat Completions streams may omit required-nullable choices[].finish_reason before their terminal chunk, so ProxAI inserts null for the missing field.
  • GLM 5.1 Anthropic-compatible streams may emit server_tool_use with only one counter, so ProxAI fills the absent web_fetch_requests or web_search_requests counter with 0.

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.