Skip to main content

gproxy_protocol/protocol/claude/
models.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::common::{AnthropicBetaHeaders, ClaudeModel, JsonObject, ModelObjectType};
7
8pub type ListModelsRequestHeaders = AnthropicBetaHeaders;
9pub type RetrieveModelRequestHeaders = AnthropicBetaHeaders;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
12#[non_exhaustive]
13pub struct ListModelsQuery {
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub after_id: Option<String>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub before_id: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub limit: Option<u64>,
20    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
21    pub extra: JsonObject,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
25#[non_exhaustive]
26pub struct RetrieveModelPath {
27    pub model_id: ClaudeModel,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
31#[non_exhaustive]
32pub struct ListModelsResponse {
33    pub data: Vec<ModelInfo>,
34    pub first_id: String,
35    pub has_more: bool,
36    pub last_id: String,
37    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
38    pub extra: JsonObject,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
42#[non_exhaustive]
43pub struct ModelInfo {
44    pub id: ClaudeModel,
45    pub allowed_fallback_models: Option<Vec<ClaudeModel>>,
46    #[serde(rename = "type")]
47    pub type_: ModelObjectType,
48    pub created_at: String,
49    pub display_name: String,
50    pub max_input_tokens: Option<u64>,
51    pub max_tokens: Option<u64>,
52    pub capabilities: Option<ModelCapabilities>,
53    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
54    pub extra: JsonObject,
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
58#[non_exhaustive]
59pub struct ModelCapabilities {
60    pub batch: CapabilitySupport,
61    pub citations: CapabilitySupport,
62    pub code_execution: CapabilitySupport,
63    pub context_management: ContextManagementCapability,
64    pub effort: EffortCapability,
65    pub image_input: CapabilitySupport,
66    pub pdf_input: CapabilitySupport,
67    pub structured_outputs: CapabilitySupport,
68    pub thinking: ThinkingCapability,
69    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
70    pub extra: JsonObject,
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
74#[non_exhaustive]
75pub struct CapabilitySupport {
76    pub supported: bool,
77    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
78    pub extra: JsonObject,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
82#[non_exhaustive]
83pub struct ContextManagementCapability {
84    pub supported: bool,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub clear_thinking_20251015: Option<CapabilitySupport>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub clear_tool_uses_20250919: Option<CapabilitySupport>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub compact_20260112: Option<CapabilitySupport>,
91    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
92    pub extra: JsonObject,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
96#[non_exhaustive]
97pub struct EffortCapability {
98    pub supported: bool,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub low: Option<CapabilitySupport>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub medium: Option<CapabilitySupport>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub high: Option<CapabilitySupport>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub xhigh: Option<CapabilitySupport>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub max: Option<CapabilitySupport>,
109    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
110    pub extra: JsonObject,
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
114#[non_exhaustive]
115pub struct ThinkingCapability {
116    pub supported: bool,
117    pub types: ThinkingTypes,
118    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
119    pub extra: JsonObject,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
123#[non_exhaustive]
124pub struct ThinkingTypes {
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub adaptive: Option<CapabilitySupport>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub enabled: Option<CapabilitySupport>,
129    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
130    pub extra: JsonObject,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
134#[non_exhaustive]
135pub struct ModelError {
136    #[serde(rename = "type")]
137    pub type_: String,
138    pub message: String,
139    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
140    pub extra: BTreeMap<String, Value>,
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn beta_model_info_decodes_nullable_metadata() {
149        let model: ModelInfo = serde_json::from_str(
150            r#"{
151                "id": "claude-test",
152                "allowed_fallback_models": null,
153                "type": "model",
154                "created_at": "2026-01-01T00:00:00Z",
155                "display_name": "Claude Test",
156                "max_input_tokens": null,
157                "max_tokens": null,
158                "capabilities": null
159            }"#,
160        )
161        .expect("decode nullable beta model metadata");
162
163        assert!(model.allowed_fallback_models.is_none());
164        assert!(model.max_input_tokens.is_none());
165        assert!(model.max_tokens.is_none());
166        assert!(model.capabilities.is_none());
167    }
168}