From 365bec868ce7574f76aec9746599cbac0ecfe2d7 Mon Sep 17 00:00:00 2001 From: Alex-wuhu Date: Tue, 31 Mar 2026 12:47:41 +0800 Subject: [PATCH 1/2] Add Novita AI as LLM provider - Add NOVITA_BASE_URL constant to model_catalog.rs - Register novita provider in provider_defaults() with OpenAI-compatible endpoint - Add novita to known_providers() list - Add novita to detect_available_provider() auto-detection probe - Document NOVITA_API_KEY in .env.example Novita AI uses an OpenAI-compatible API at https://api.novita.ai/openai/v1 Default model: moonshotai/kimi-k2.5 --- .env.example | 3 +++ crates/openfang-runtime/src/drivers/mod.rs | 18 +++++++++++++----- crates/openfang-types/src/model_catalog.rs | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index a7409061..6a3882a2 100644 --- a/.env.example +++ b/.env.example @@ -32,6 +32,9 @@ # Fireworks AI # FIREWORKS_API_KEY=... +# Novita AI (multi-model gateway) +# NOVITA_API_KEY=... + # ─── Local LLM Providers (no API key needed) ───────────────────────── # Ollama (default: http://localhost:11434) diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 2df8923d..14e5d81d 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -18,10 +18,10 @@ use openfang_types::model_catalog::{ AI21_BASE_URL, ANTHROPIC_BASE_URL, AZURE_OPENAI_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL, FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, LMSTUDIO_BASE_URL, - MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NVIDIA_NIM_BASE_URL, OLLAMA_BASE_URL, - OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL, - REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL, - VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, + MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, NVIDIA_NIM_BASE_URL, NOVITA_BASE_URL, + OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, + QWEN_BASE_URL, REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, + VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, }; use std::sync::Arc; @@ -222,6 +222,11 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "NVIDIA_API_KEY", key_required: true, }), + "novita" | "novita-ai" => Some(ProviderDefaults { + base_url: NOVITA_BASE_URL, + api_key_env: "NOVITA_API_KEY", + key_required: true, + }), "azure" | "azure-openai" => Some(ProviderDefaults { base_url: AZURE_OPENAI_BASE_URL, api_key_env: "AZURE_OPENAI_API_KEY", @@ -530,6 +535,7 @@ pub fn detect_available_provider() -> Option<(&'static str, &'static str, &'stat "PERPLEXITY_API_KEY", ), ("cohere", "command-r-plus", "COHERE_API_KEY"), + ("novita", "moonshotai/kimi-k2.5", "NOVITA_API_KEY"), ]; for &(provider, model, env_var) in PROBE_ORDER { if std::env::var(env_var) @@ -587,6 +593,7 @@ pub fn known_providers() -> &'static [&'static str] { "chutes", "venice", "nvidia", + "novita", "codex", "claude-code", "qwen-code", @@ -691,11 +698,12 @@ mod tests { assert!(providers.contains(&"volcengine")); assert!(providers.contains(&"chutes")); assert!(providers.contains(&"nvidia")); + assert!(providers.contains(&"novita")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); assert!(providers.contains(&"qwen-code")); assert!(providers.contains(&"azure")); - assert_eq!(providers.len(), 37); + assert_eq!(providers.len(), 38); } #[test] diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs index a7d2627c..1d9abfa3 100644 --- a/crates/openfang-types/src/model_catalog.rs +++ b/crates/openfang-types/src/model_catalog.rs @@ -32,6 +32,10 @@ pub const REPLICATE_BASE_URL: &str = "https://api.replicate.com/v1"; pub const VENICE_BASE_URL: &str = "https://api.venice.ai/api/v1"; pub const NVIDIA_NIM_BASE_URL: &str = "https://integrate.api.nvidia.com/v1"; +// ── Novita AI ───────────────────────────────────────────────────── +/// Novita AI OpenAI-compatible endpoint. +pub const NOVITA_BASE_URL: &str = "https://api.novita.ai/openai/v1"; + // ── GitHub Copilot ────────────────────────────────────────────── pub const GITHUB_COPILOT_BASE_URL: &str = "https://api.githubcopilot.com"; From 4714efb9e42e0503c20ee11893cdbcf6d9635247 Mon Sep 17 00:00:00 2001 From: Alex-wuhu Date: Tue, 31 Mar 2026 14:26:49 +0800 Subject: [PATCH 2/2] test: add unit tests for Novita provider defaults, alias, and driver creation - test_provider_defaults_novita: verify base_url, api_key_env, key_required - test_provider_defaults_novita_ai_alias: verify "novita-ai" alias resolves - test_novita_provider_with_env_key: driver creation succeeds with env key - test_novita_provider_no_key_errors: driver creation fails without key Co-Authored-By: Claude Opus 4.6 --- crates/openfang-runtime/src/drivers/mod.rs | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 14e5d81d..5ebab536 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -744,6 +744,52 @@ mod tests { assert!(d.key_required); } + #[test] + fn test_provider_defaults_novita() { + let d = provider_defaults("novita").unwrap(); + assert_eq!(d.base_url, "https://api.novita.ai/openai/v1"); + assert_eq!(d.api_key_env, "NOVITA_API_KEY"); + assert!(d.key_required); + } + + #[test] + fn test_provider_defaults_novita_ai_alias() { + let d = provider_defaults("novita-ai").unwrap(); + assert_eq!(d.base_url, "https://api.novita.ai/openai/v1"); + assert_eq!(d.api_key_env, "NOVITA_API_KEY"); + assert!(d.key_required); + } + + #[test] + fn test_novita_provider_with_env_key() { + let unique_key = "test-novita-key-12345"; + std::env::set_var("NOVITA_API_KEY", unique_key); + let config = DriverConfig { + provider: "novita".to_string(), + api_key: None, + base_url: None, + skip_permissions: true, + }; + let driver = create_driver(&config); + assert!( + driver.is_ok(), + "Novita provider with env var should succeed" + ); + std::env::remove_var("NOVITA_API_KEY"); + } + + #[test] + fn test_novita_provider_no_key_errors() { + let config = DriverConfig { + provider: "novita".to_string(), + api_key: None, + base_url: None, + skip_permissions: true, + }; + let driver = create_driver(&config); + assert!(driver.is_err()); + } + #[test] fn test_nvidia_provider_with_env_key() { // NVIDIA NIM is a known provider — set API key and verify driver creation succeeds.