#![allow(dead_code)]
use crate::config::{ApiProvider, ProviderCapability, RequestPayloadMode, provider_capability};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthModel {
EnvVar(&'static str),
OAuth,
BuiltInKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequestDialect {
OpenAiCompatible,
DeepSeekNative,
Anthropic,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapabilityDescriptor {
pub context_window: u32,
pub max_output: u32,
pub supports_thinking: bool,
pub supports_tools: bool,
pub supports_streaming: bool,
pub supports_cache: bool,
}
impl CapabilityDescriptor {
#[must_use]
pub fn from_capability(cap: &ProviderCapability) -> Self {
Self {
context_window: cap.context_window,
max_output: cap.max_output,
supports_thinking: cap.thinking_supported,
supports_tools: true,
supports_streaming: true,
supports_cache: cap.cache_telemetry_supported,
}
}
}
#[must_use]
pub fn dialect_for_payload_mode(mode: RequestPayloadMode) -> RequestDialect {
match mode {
RequestPayloadMode::AnthropicMessages => RequestDialect::Anthropic,
RequestPayloadMode::ChatCompletions | RequestPayloadMode::Responses => {
RequestDialect::OpenAiCompatible
}
}
}
pub trait ProviderAdapter {
fn provider(&self) -> ApiProvider;
fn resolved_model(&self) -> &str;
fn capability(&self) -> CapabilityDescriptor {
let cap = provider_capability(self.provider(), self.resolved_model());
CapabilityDescriptor::from_capability(&cap)
}
fn auth_model(&self) -> AuthModel;
fn request_dialect(&self) -> RequestDialect;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConformanceError {
ZeroContextWindow,
ZeroMaxOutput,
MaxOutputExceedsContextWindow {
max_output: u32,
context_window: u32,
},
}
impl std::fmt::Display for ConformanceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroContextWindow => write!(f, "context_window must be > 0"),
Self::ZeroMaxOutput => write!(f, "max_output must be > 0"),
Self::MaxOutputExceedsContextWindow {
max_output,
context_window,
} => write!(
f,
"max_output ({max_output}) must be <= context_window ({context_window})"
),
}
}
}
impl std::error::Error for ConformanceError {}
pub fn check_adapter_conformance<A: ProviderAdapter>(adapter: &A) -> Result<(), ConformanceError> {
let cap = adapter.capability();
if cap.context_window == 0 {
return Err(ConformanceError::ZeroContextWindow);
}
if cap.max_output == 0 {
return Err(ConformanceError::ZeroMaxOutput);
}
if cap.max_output > cap.context_window {
return Err(ConformanceError::MaxOutputExceedsContextWindow {
max_output: cap.max_output,
context_window: cap.context_window,
});
}
Ok(())
}
pub fn assert_adapter_conformance<A: ProviderAdapter>(adapter: &A) {
if let Err(err) = check_adapter_conformance(adapter) {
panic!(
"provider adapter for {:?} failed conformance: {err}",
adapter.provider()
);
}
}
#[derive(Debug, Clone)]
pub struct DeepSeekAdapter {
resolved_model: String,
}
impl DeepSeekAdapter {
pub const API_KEY_ENV: &'static str = "DEEPSEEK_API_KEY";
#[must_use]
pub fn new(resolved_model: impl Into<String>) -> Self {
Self {
resolved_model: resolved_model.into(),
}
}
}
impl ProviderAdapter for DeepSeekAdapter {
fn provider(&self) -> ApiProvider {
ApiProvider::Deepseek
}
fn resolved_model(&self) -> &str {
&self.resolved_model
}
fn auth_model(&self) -> AuthModel {
AuthModel::EnvVar(Self::API_KEY_ENV)
}
fn request_dialect(&self) -> RequestDialect {
RequestDialect::DeepSeekNative
}
}
#[derive(Debug, Clone)]
pub struct OpenAiCompatibleAdapter {
resolved_model: String,
}
impl OpenAiCompatibleAdapter {
pub const API_KEY_ENV: &'static str = "OPENAI_API_KEY";
#[must_use]
pub fn new(resolved_model: impl Into<String>) -> Self {
Self {
resolved_model: resolved_model.into(),
}
}
}
impl ProviderAdapter for OpenAiCompatibleAdapter {
fn provider(&self) -> ApiProvider {
ApiProvider::Openai
}
fn resolved_model(&self) -> &str {
&self.resolved_model
}
fn auth_model(&self) -> AuthModel {
AuthModel::EnvVar(Self::API_KEY_ENV)
}
fn request_dialect(&self) -> RequestDialect {
let cap = provider_capability(self.provider(), self.resolved_model());
dialect_for_payload_mode(cap.request_payload_mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deepseek_adapter_conforms() {
let adapter = DeepSeekAdapter::new("deepseek-v4-flash");
assert_adapter_conformance(&adapter);
assert!(check_adapter_conformance(&adapter).is_ok());
}
#[test]
fn openai_adapter_conforms() {
let adapter = OpenAiCompatibleAdapter::new("gpt-4o");
assert_adapter_conformance(&adapter);
assert!(check_adapter_conformance(&adapter).is_ok());
}
#[test]
fn deepseek_markers_are_as_expected() {
let adapter = DeepSeekAdapter::new("deepseek-v4-flash");
assert_eq!(adapter.provider(), ApiProvider::Deepseek);
assert_eq!(adapter.auth_model(), AuthModel::EnvVar("DEEPSEEK_API_KEY"));
assert_eq!(adapter.request_dialect(), RequestDialect::DeepSeekNative);
}
#[test]
fn openai_markers_are_as_expected() {
let adapter = OpenAiCompatibleAdapter::new("gpt-4o");
assert_eq!(adapter.provider(), ApiProvider::Openai);
assert_eq!(adapter.auth_model(), AuthModel::EnvVar("OPENAI_API_KEY"));
assert_eq!(adapter.request_dialect(), RequestDialect::OpenAiCompatible);
}
#[test]
fn capability_descriptor_matches_canonical_matrix() {
let adapter = DeepSeekAdapter::new("deepseek-v4-flash");
let canonical = provider_capability(ApiProvider::Deepseek, "deepseek-v4-flash");
let descriptor = adapter.capability();
assert_eq!(descriptor.context_window, canonical.context_window);
assert_eq!(descriptor.max_output, canonical.max_output);
assert_eq!(descriptor.supports_thinking, canonical.thinking_supported);
assert_eq!(
descriptor.supports_cache,
canonical.cache_telemetry_supported
);
assert!(descriptor.supports_tools);
assert!(descriptor.supports_streaming);
}
#[test]
fn deepseek_v4_flash_has_expected_envelope() {
let adapter = DeepSeekAdapter::new("deepseek-v4-flash");
let cap = adapter.capability();
assert_eq!(cap.context_window, 1_000_000);
assert_eq!(cap.max_output, 384_000);
assert!(cap.supports_thinking);
assert!(cap.supports_cache);
}
#[test]
fn payload_mode_dialect_mapping() {
assert_eq!(
dialect_for_payload_mode(RequestPayloadMode::ChatCompletions),
RequestDialect::OpenAiCompatible
);
assert_eq!(
dialect_for_payload_mode(RequestPayloadMode::Responses),
RequestDialect::OpenAiCompatible
);
assert_eq!(
dialect_for_payload_mode(RequestPayloadMode::AnthropicMessages),
RequestDialect::Anthropic
);
}
#[test]
fn conformance_rejects_zero_context_window() {
struct BrokenAdapter;
impl ProviderAdapter for BrokenAdapter {
fn provider(&self) -> ApiProvider {
ApiProvider::Openai
}
fn resolved_model(&self) -> &str {
"broken"
}
fn capability(&self) -> CapabilityDescriptor {
CapabilityDescriptor {
context_window: 0,
max_output: 10,
supports_thinking: false,
supports_tools: true,
supports_streaming: true,
supports_cache: false,
}
}
fn auth_model(&self) -> AuthModel {
AuthModel::OAuth
}
fn request_dialect(&self) -> RequestDialect {
RequestDialect::OpenAiCompatible
}
}
assert_eq!(
check_adapter_conformance(&BrokenAdapter),
Err(ConformanceError::ZeroContextWindow)
);
}
#[test]
fn conformance_rejects_max_output_exceeding_context_window() {
struct BrokenAdapter;
impl ProviderAdapter for BrokenAdapter {
fn provider(&self) -> ApiProvider {
ApiProvider::Openai
}
fn resolved_model(&self) -> &str {
"broken"
}
fn capability(&self) -> CapabilityDescriptor {
CapabilityDescriptor {
context_window: 100,
max_output: 200,
supports_thinking: false,
supports_tools: true,
supports_streaming: true,
supports_cache: false,
}
}
fn auth_model(&self) -> AuthModel {
AuthModel::BuiltInKey
}
fn request_dialect(&self) -> RequestDialect {
RequestDialect::OpenAiCompatible
}
}
assert_eq!(
check_adapter_conformance(&BrokenAdapter),
Err(ConformanceError::MaxOutputExceedsContextWindow {
max_output: 200,
context_window: 100,
})
);
}
#[test]
#[should_panic(expected = "failed conformance")]
fn assert_conformance_panics_on_violation() {
struct BrokenAdapter;
impl ProviderAdapter for BrokenAdapter {
fn provider(&self) -> ApiProvider {
ApiProvider::Openai
}
fn resolved_model(&self) -> &str {
"broken"
}
fn capability(&self) -> CapabilityDescriptor {
CapabilityDescriptor {
context_window: 10,
max_output: 0,
supports_thinking: false,
supports_tools: true,
supports_streaming: true,
supports_cache: false,
}
}
fn auth_model(&self) -> AuthModel {
AuthModel::OAuth
}
fn request_dialect(&self) -> RequestDialect {
RequestDialect::OpenAiCompatible
}
}
assert_adapter_conformance(&BrokenAdapter);
}
}