Skip to main content

oxicode_catalog/
api.rs

1//! The `Api` enum — the wire-format / protocol dialect spoken to a particular
2//! LLM provider.
3//!
4//! Rust port of omp's `KnownApi` union (`packages/catalog/src/types.ts:8-22`).
5//! Selects which streaming transport function handles a model; carries **no
6//! provider identity** (identity lives in `oxicode-ai`'s `ProviderDefinition`
7//! registry, mirroring omp's three-way split: transport / auth-login /
8//! model-host metadata).
9//!
10//! Each enum variant maps to a concrete dispatch in
11//! `oxicode-ai/src/providers/register_builtins::build_builtin_transport`. The
12//! two remaining gap variants are `Api::OpenAiCodexResponses` and
13//! `Api::GoogleGeminiCli`: Codex reuses the OpenAI Responses transport, and
14//! Gemini CLI is a typed stub (`GeminiCliProvider` returning
15//! `ProviderError::NotImplemented`) until a real protocol is integrated.
16
17use serde::{Deserialize, Serialize};
18use std::fmt;
19
20/// Provider API identifier.
21///
22/// Selects the wire-format / protocol dialect spoken to a particular LLM
23/// provider. The canonical 14 `KnownApi` dialects from omp; `Mistral` is
24/// intentionally absent — omp treats Mistral as `openai-completions`-compatible
25/// (no separate dialect).
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[non_exhaustive]
28pub enum Api {
29    /// OpenAI Chat Completions API (also: Mistral, DeepSeek, Together, …).
30    #[serde(rename = "openai-completions")]
31    OpenAiCompletions,
32    /// OpenAI Responses API.
33    #[serde(rename = "openai-responses")]
34    OpenAiResponses,
35    /// OpenRouter.
36    #[serde(rename = "openrouter")]
37    OpenRouter,
38    /// OpenAI Codex Responses API.
39    #[serde(rename = "openai-codex-responses")]
40    OpenAiCodexResponses,
41    /// Azure OpenAI Responses API.
42    #[serde(rename = "azure-openai-responses")]
43    AzureOpenAiResponses,
44    /// Anthropic Messages API.
45    #[serde(rename = "anthropic-messages")]
46    AnthropicMessages,
47    /// AWS Bedrock Converse Stream API.
48    #[serde(rename = "bedrock-converse-stream")]
49    BedrockConverseStream,
50    /// Google Generative AI (Gemini) API.
51    #[serde(rename = "google-generative-ai")]
52    GoogleGenerativeAi,
53    /// Google Gemini CLI (remote-AGENT protocol).
54    #[serde(rename = "google-gemini-cli")]
55    GoogleGeminiCli,
56    /// Google Vertex AI endpoint.
57    #[serde(rename = "google-vertex")]
58    GoogleVertex,
59    /// Ollama chat API (local server).
60    #[serde(rename = "ollama-chat")]
61    OllamaChat,
62    /// Cursor (remote-AGENT protocol).
63    #[serde(rename = "cursor-agent")]
64    CursorAgent,
65    /// GitLab Duo REST proxy (AI Gateway — delegates to Anthropic/OpenAI).
66    #[serde(rename = "gitlab-duo")]
67    GitLabDuo,
68    /// GitLab Duo Agent (WebSocket workflow protocol).
69    #[serde(rename = "gitlab-duo-agent")]
70    GitLabDuoAgent,
71    /// Devin (remote-AGENT protocol).
72    #[serde(rename = "devin-agent")]
73    DevinAgent,
74}
75
76impl fmt::Display for Api {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        let s = match self {
79            Api::OpenAiCompletions => "openai-completions",
80            Api::OpenAiResponses => "openai-responses",
81            Api::OpenRouter => "openrouter",
82            Api::OpenAiCodexResponses => "openai-codex-responses",
83            Api::AzureOpenAiResponses => "azure-openai-responses",
84            Api::AnthropicMessages => "anthropic-messages",
85            Api::BedrockConverseStream => "bedrock-converse-stream",
86            Api::GoogleGenerativeAi => "google-generative-ai",
87            Api::GoogleGeminiCli => "google-gemini-cli",
88            Api::GoogleVertex => "google-vertex",
89            Api::OllamaChat => "ollama-chat",
90            Api::CursorAgent => "cursor-agent",
91            Api::GitLabDuo => "gitlab-duo",
92            Api::GitLabDuoAgent => "gitlab-duo-agent",
93            Api::DevinAgent => "devin-agent",
94        };
95        f.write_str(s)
96    }
97}
98
99impl Api {
100    /// Parse a kebab-case dialect string (omp `KnownApi` serialization) into
101    /// an `Api`. Returns `None` for unrecognized strings — the single
102    /// authoritative parser (callers decide the fallback, e.g.
103    /// OpenAI-compatible default for unknown gateways/aggregators).
104    ///
105    /// This exists so the dialect↔string mapping lives with the enum (not
106    /// duplicated as stale `parse_api` matches across crates that miss new
107    /// variants).
108    pub fn from_kebab_str(s: &str) -> Option<Self> {
109        Some(match s {
110            "openai-completions" => Api::OpenAiCompletions,
111            "openai-responses" => Api::OpenAiResponses,
112            "openrouter" => Api::OpenRouter,
113            "openai-codex-responses" => Api::OpenAiCodexResponses,
114            "azure-openai-responses" => Api::AzureOpenAiResponses,
115            "anthropic-messages" => Api::AnthropicMessages,
116            "bedrock-converse-stream" => Api::BedrockConverseStream,
117            "google-generative-ai" => Api::GoogleGenerativeAi,
118            "google-gemini-cli" => Api::GoogleGeminiCli,
119            "google-vertex" => Api::GoogleVertex,
120            "ollama-chat" => Api::OllamaChat,
121            "cursor-agent" => Api::CursorAgent,
122            "gitlab-duo" => Api::GitLabDuo,
123            "gitlab-duo-agent" => Api::GitLabDuoAgent,
124            "devin-agent" => Api::DevinAgent,
125            _ => return None,
126        })
127    }
128}