diff --git a/crates/openshell-cli/src/main.rs b/crates/openshell-cli/src/main.rs index 84a323b5..f5377bd4 100644 --- a/crates/openshell-cli/src/main.rs +++ b/crates/openshell-cli/src/main.rs @@ -580,6 +580,7 @@ enum CliProviderType { Generic, Openai, Anthropic, + BitdeerAi, Nvidia, Gitlab, Github, @@ -610,6 +611,7 @@ impl CliProviderType { Self::Generic => "generic", Self::Openai => "openai", Self::Anthropic => "anthropic", + Self::BitdeerAi => "bitdeer-ai", Self::Nvidia => "nvidia", Self::Gitlab => "gitlab", Self::Github => "github", diff --git a/crates/openshell-core/src/inference.rs b/crates/openshell-core/src/inference.rs index a06c427f..41741352 100644 --- a/crates/openshell-core/src/inference.rs +++ b/crates/openshell-core/src/inference.rs @@ -86,6 +86,16 @@ static NVIDIA_PROFILE: InferenceProviderProfile = InferenceProviderProfile { default_headers: &[], }; +static BITDEER_AI_PROFILE: InferenceProviderProfile = InferenceProviderProfile { + provider_type: "bitdeer-ai", + default_base_url: "https://api-inference.bitdeer.ai/v1", + protocols: OPENAI_PROTOCOLS, + credential_key_names: &["BITDEERAI_API_KEY"], + base_url_config_keys: &["BITDEERAI_BASE_URL"], + auth: AuthHeader::Bearer, + default_headers: &[], +}; + /// Look up the inference provider profile for a given provider type. /// /// Returns `None` for provider types that don't support inference routing @@ -95,6 +105,7 @@ pub fn profile_for(provider_type: &str) -> Option<&'static InferenceProviderProf "openai" => Some(&OPENAI_PROFILE), "anthropic" => Some(&ANTHROPIC_PROFILE), "nvidia" => Some(&NVIDIA_PROFILE), + "bitdeer-ai" => Some(&BITDEER_AI_PROFILE), _ => None, } } @@ -176,6 +187,7 @@ mod tests { assert!(profile_for("openai").is_some()); assert!(profile_for("anthropic").is_some()); assert!(profile_for("nvidia").is_some()); + assert!(profile_for("bitdeer-ai").is_some()); assert!(profile_for("OpenAI").is_some()); // case insensitive } @@ -206,4 +218,17 @@ mod tests { assert_eq!(auth, AuthHeader::Bearer); assert!(headers.is_empty()); } + + #[test] + fn bitdeer_ai_profile_has_correct_defaults() { + let profile = profile_for("bitdeer-ai").expect("bitdeer-ai profile should exist"); + assert_eq!(profile.provider_type, "bitdeer-ai"); + assert_eq!( + profile.default_base_url, + "https://api-inference.bitdeer.ai/v1" + ); + assert_eq!(profile.credential_key_names, &["BITDEERAI_API_KEY"]); + assert_eq!(profile.auth, AuthHeader::Bearer); + assert!(profile.default_headers.is_empty()); + } } diff --git a/crates/openshell-providers/src/lib.rs b/crates/openshell-providers/src/lib.rs index 143466d2..bb04f033 100644 --- a/crates/openshell-providers/src/lib.rs +++ b/crates/openshell-providers/src/lib.rs @@ -81,6 +81,7 @@ impl ProviderRegistry { registry.register(providers::generic::GenericProvider); registry.register(providers::openai::OpenaiProvider); registry.register(providers::anthropic::AnthropicProvider); + registry.register(providers::bitdeer_ai::BitdeerAiProvider); registry.register(providers::nvidia::NvidiaProvider); registry.register(providers::gitlab::GitlabProvider); registry.register(providers::github::GithubProvider); @@ -132,6 +133,7 @@ pub fn normalize_provider_type(input: &str) -> Option<&'static str> { "generic" => Some("generic"), "openai" => Some("openai"), "anthropic" => Some("anthropic"), + "bitdeer-ai" | "bitdeer" => Some("bitdeer-ai"), "nvidia" => Some("nvidia"), "gitlab" | "glab" => Some("gitlab"), "github" | "gh" => Some("github"), @@ -164,6 +166,8 @@ mod tests { assert_eq!(normalize_provider_type("openai"), Some("openai")); assert_eq!(normalize_provider_type("anthropic"), Some("anthropic")); assert_eq!(normalize_provider_type("nvidia"), Some("nvidia")); + assert_eq!(normalize_provider_type("bitdeer-ai"), Some("bitdeer-ai")); + assert_eq!(normalize_provider_type("bitdeer"), Some("bitdeer-ai")); assert_eq!(normalize_provider_type("unknown"), None); } diff --git a/crates/openshell-providers/src/providers/bitdeer_ai.rs b/crates/openshell-providers/src/providers/bitdeer_ai.rs new file mode 100644 index 00000000..aad808b1 --- /dev/null +++ b/crates/openshell-providers/src/providers/bitdeer_ai.rs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + ProviderDiscoverySpec, ProviderError, ProviderPlugin, RealDiscoveryContext, discover_with_spec, +}; + +pub struct BitdeerAiProvider; + +pub const SPEC: ProviderDiscoverySpec = ProviderDiscoverySpec { + id: "bitdeer-ai", + credential_env_vars: &["BITDEERAI_API_KEY"], +}; + +impl ProviderPlugin for BitdeerAiProvider { + fn id(&self) -> &'static str { + SPEC.id + } + + fn discover_existing(&self) -> Result, ProviderError> { + discover_with_spec(&SPEC, &RealDiscoveryContext) + } + + fn credential_env_vars(&self) -> &'static [&'static str] { + SPEC.credential_env_vars + } +} + +#[cfg(test)] +mod tests { + use super::SPEC; + use crate::discover_with_spec; + use crate::test_helpers::MockDiscoveryContext; + + #[test] + fn discovers_bitdeer_ai_env_credentials() { + let ctx = MockDiscoveryContext::new().with_env("BITDEERAI_API_KEY", "bd-test-key-123"); + let discovered = discover_with_spec(&SPEC, &ctx) + .expect("discovery") + .expect("provider"); + assert_eq!( + discovered.credentials.get("BITDEERAI_API_KEY"), + Some(&"bd-test-key-123".to_string()) + ); + } +} diff --git a/crates/openshell-providers/src/providers/mod.rs b/crates/openshell-providers/src/providers/mod.rs index 8ab52ed9..852f90a4 100644 --- a/crates/openshell-providers/src/providers/mod.rs +++ b/crates/openshell-providers/src/providers/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod anthropic; +pub mod bitdeer_ai; pub mod claude; pub mod codex; pub mod generic; diff --git a/docs/sandboxes/manage-providers.md b/docs/sandboxes/manage-providers.md index d238e3ee..87284408 100644 --- a/docs/sandboxes/manage-providers.md +++ b/docs/sandboxes/manage-providers.md @@ -136,6 +136,7 @@ The following provider types are supported. | `github` | `GITHUB_TOKEN`, `GH_TOKEN` | GitHub API, `gh` CLI — refer to {doc}`/tutorials/github-sandbox` | | `gitlab` | `GITLAB_TOKEN`, `GLAB_TOKEN`, `CI_JOB_TOKEN` | GitLab API, `glab` CLI | | `nvidia` | `NVIDIA_API_KEY` | NVIDIA API Catalog | +| `bitdeer-ai` | `BITDEERAI_API_KEY` | Bitdeer AI inference API | | `generic` | User-defined | Any service with custom credentials | :::{tip}