gproxy-protocol 2.6.4

GPROXY protocol types and endpoint metadata
Documentation
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::common::{AnthropicBetaHeaders, ClaudeModel, JsonObject, ModelObjectType};

pub type ListModelsRequestHeaders = AnthropicBetaHeaders;
pub type RetrieveModelRequestHeaders = AnthropicBetaHeaders;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ListModelsQuery {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub after_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub before_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u64>,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct RetrieveModelPath {
    pub model_id: ClaudeModel,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ListModelsResponse {
    pub data: Vec<ModelInfo>,
    pub first_id: String,
    pub has_more: bool,
    pub last_id: String,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ModelInfo {
    pub id: ClaudeModel,
    pub allowed_fallback_models: Option<Vec<ClaudeModel>>,
    #[serde(rename = "type")]
    pub type_: ModelObjectType,
    pub created_at: String,
    pub display_name: String,
    pub max_input_tokens: Option<u64>,
    pub max_tokens: Option<u64>,
    pub capabilities: Option<ModelCapabilities>,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ModelCapabilities {
    pub batch: CapabilitySupport,
    pub citations: CapabilitySupport,
    pub code_execution: CapabilitySupport,
    pub context_management: ContextManagementCapability,
    pub effort: EffortCapability,
    pub image_input: CapabilitySupport,
    pub pdf_input: CapabilitySupport,
    pub structured_outputs: CapabilitySupport,
    pub thinking: ThinkingCapability,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct CapabilitySupport {
    pub supported: bool,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ContextManagementCapability {
    pub supported: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub clear_thinking_20251015: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub clear_tool_uses_20250919: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub compact_20260112: Option<CapabilitySupport>,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct EffortCapability {
    pub supported: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub low: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub medium: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub high: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub xhigh: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max: Option<CapabilitySupport>,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ThinkingCapability {
    pub supported: bool,
    pub types: ThinkingTypes,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ThinkingTypes {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub adaptive: Option<CapabilitySupport>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub enabled: Option<CapabilitySupport>,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: JsonObject,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
#[non_exhaustive]
pub struct ModelError {
    #[serde(rename = "type")]
    pub type_: String,
    pub message: String,
    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
    pub extra: BTreeMap<String, Value>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn beta_model_info_decodes_nullable_metadata() {
        let model: ModelInfo = serde_json::from_str(
            r#"{
                "id": "claude-test",
                "allowed_fallback_models": null,
                "type": "model",
                "created_at": "2026-01-01T00:00:00Z",
                "display_name": "Claude Test",
                "max_input_tokens": null,
                "max_tokens": null,
                "capabilities": null
            }"#,
        )
        .expect("decode nullable beta model metadata");

        assert!(model.allowed_fallback_models.is_none());
        assert!(model.max_input_tokens.is_none());
        assert!(model.max_tokens.is_none());
        assert!(model.capabilities.is_none());
    }
}