Skip to main content

rab/provider/
compat.rs

1//! Rich compatibility flags matching pi's OpenAICompletionsCompat structure.
2//! Deserialized from the `compat` field in models.json, then serialized into
3//! `ModelConfig::headers["_rab_compat"]` for our custom provider to read.
4
5use serde::{Deserialize, Serialize};
6
7/// Thinking format strategies (maps pi's `thinkingFormat`).
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum RabThinkingFormat {
11    #[default]
12    #[serde(rename = "openai")]
13    OpenAi,
14    #[serde(rename = "openrouter")]
15    OpenRouter,
16    #[serde(rename = "deepseek")]
17    DeepSeek,
18    #[serde(rename = "together")]
19    Together,
20    #[serde(rename = "zai")]
21    Zai,
22    #[serde(rename = "qwen")]
23    Qwen,
24    #[serde(rename = "chat-template")]
25    ChatTemplate,
26    #[serde(rename = "qwen-chat-template")]
27    QwenChatTemplate,
28    #[serde(rename = "string-thinking")]
29    StringThinking,
30    #[serde(rename = "ant-ling")]
31    AntLing,
32}
33
34/// Max tokens field name.
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum RabMaxTokensField {
38    #[serde(rename = "max_tokens")]
39    MaxTokens,
40    #[default]
41    #[serde(rename = "max_completion_tokens")]
42    MaxCompletionTokens,
43}
44
45/// Rich compatibility flags, matching pi's `OpenAICompletionsCompat` schema.
46///
47/// All fields are optional — defaults are resolved at use time in the provider.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(default, rename_all = "camelCase")]
50pub struct RabOpenAiCompat {
51    /// Whether the provider supports the `store` parameter (OpenAI-only).
52    pub supports_store: bool,
53    /// Whether the provider supports the `developer` role vs `system`.
54    pub supports_developer_role: bool,
55    /// Whether the provider supports `reasoning_effort`.
56    pub supports_reasoning_effort: bool,
57    /// Whether the provider supports `thinking: { type }` control.
58    pub supports_thinking_control: bool,
59    /// Whether usage data is available in streaming responses.
60    pub supports_usage_in_streaming: bool,
61    /// Which field name to use for max tokens.
62    pub max_tokens_field: RabMaxTokensField,
63    /// Whether tool results must include a `name` field.
64    pub requires_tool_result_name: bool,
65    /// Whether an assistant message must be inserted after tool results.
66    pub requires_assistant_after_tool_result: bool,
67    /// Whether thinking blocks must be converted to text with `<thinking>` tags.
68    pub requires_thinking_as_text: bool,
69    /// Whether replayed assistant messages must include `reasoning_content`.
70    pub requires_reasoning_content_on_assistant_messages: bool,
71    /// How thinking/reasoning is formatted in the API.
72    pub thinking_format: RabThinkingFormat,
73    /// Whether the provider supports the `strict` field in tool definitions.
74    pub supports_strict_mode: bool,
75    /// Whether the provider supports long cache retention.
76    pub supports_long_cache_retention: bool,
77}
78
79impl Default for RabOpenAiCompat {
80    fn default() -> Self {
81        Self {
82            supports_store: true,
83            supports_developer_role: true,
84            supports_reasoning_effort: true,
85            supports_thinking_control: false,
86            supports_usage_in_streaming: true,
87            max_tokens_field: RabMaxTokensField::MaxCompletionTokens,
88            requires_tool_result_name: false,
89            requires_assistant_after_tool_result: false,
90            requires_thinking_as_text: false,
91            requires_reasoning_content_on_assistant_messages: false,
92            thinking_format: RabThinkingFormat::OpenAi,
93            supports_strict_mode: true,
94            supports_long_cache_retention: true,
95        }
96    }
97}