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}