Skip to main content

systemprompt_models/schema/
capabilities.rs

1//! Per-provider JSON-Schema capability matrices.
2//!
3//! [`ProviderCapabilities`] declares which JSON-Schema constructs a provider's
4//! tool/output schema parser accepts. It is the input to
5//! [`super::SchemaSanitizer`], which strips everything a provider does not
6//! support. The matrices live here in `shared/models` so both the gateway wire
7//! codecs and the agent-flow provider clients resolve the same authority; the
8//! wire protocol picks one via
9//! [`crate::profile::WireProtocol::schema_capabilities`].
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14use serde_json::Value;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct SchemaComposition {
18    pub allof: bool,
19    pub anyof: bool,
20    pub oneof: bool,
21    pub if_then_else: bool,
22    pub not: bool,
23}
24
25#[expect(
26    clippy::struct_excessive_bools,
27    reason = "schema feature matrix: each bool is an independent JSON-Schema construct the \
28              provider does or does not accept, not state"
29)]
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct SchemaFeatures {
32    pub references: bool,
33    pub definitions: bool,
34    pub additional_properties: bool,
35    pub const_values: bool,
36    pub exclusive_bounds: bool,
37    pub property_names: bool,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct ProviderCapabilities {
42    pub composition: SchemaComposition,
43    pub features: SchemaFeatures,
44}
45
46impl ProviderCapabilities {
47    pub const fn anthropic() -> Self {
48        Self {
49            composition: SchemaComposition {
50                allof: true,
51                anyof: true,
52                oneof: true,
53                if_then_else: true,
54                not: true,
55            },
56            features: SchemaFeatures {
57                references: true,
58                definitions: true,
59                additional_properties: true,
60                const_values: true,
61                exclusive_bounds: true,
62                property_names: true,
63            },
64        }
65    }
66
67    pub const fn openai() -> Self {
68        Self {
69            composition: SchemaComposition {
70                allof: true,
71                anyof: true,
72                oneof: true,
73                if_then_else: false,
74                not: false,
75            },
76            features: SchemaFeatures {
77                references: true,
78                definitions: true,
79                additional_properties: true,
80                const_values: true,
81                exclusive_bounds: true,
82                property_names: true,
83            },
84        }
85    }
86
87    pub const fn gemini() -> Self {
88        Self {
89            composition: SchemaComposition {
90                allof: false,
91                anyof: true,
92                oneof: false,
93                if_then_else: false,
94                not: false,
95            },
96            features: SchemaFeatures {
97                references: false,
98                definitions: false,
99                additional_properties: false,
100                const_values: false,
101                exclusive_bounds: false,
102                property_names: false,
103            },
104        }
105    }
106
107    pub fn requires_transformation(&self, schema: &Value) -> bool {
108        if let Some(obj) = schema.as_object() {
109            if obj.contains_key("allOf") && !self.composition.allof {
110                return true;
111            }
112            if obj.contains_key("anyOf") && !self.composition.anyof {
113                return true;
114            }
115            if obj.contains_key("oneOf") && !self.composition.oneof {
116                return true;
117            }
118            if obj.contains_key("if") && !self.composition.if_then_else {
119                return true;
120            }
121            if obj.contains_key("$ref") && !self.features.references {
122                return true;
123            }
124            if (obj.contains_key("definitions") || obj.contains_key("$defs"))
125                && !self.features.definitions
126            {
127                return true;
128            }
129            if obj.contains_key("not") && !self.composition.not {
130                return true;
131            }
132        }
133        false
134    }
135}