Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cli/src/commands/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::config::{ProfileConfig, ProviderType};
use crate::onboarding::auth_flow::{AuthFlowConfig, run_provider_auth_flow};
use crate::onboarding::config_templates::{
generate_anthropic_profile, generate_gemini_profile, generate_github_copilot_profile,
generate_openai_profile, generate_openrouter_profile,
generate_openai_profile, generate_openrouter_profile, generate_requesty_profile,
};
use crate::onboarding::menu::select_option_no_header;
use crate::onboarding::navigation::NavResult;
Expand Down Expand Up @@ -362,6 +362,7 @@ async fn handle_non_interactive_api_setup(
"openai" => generate_openai_profile(),
"gemini" => generate_gemini_profile(),
"openrouter" => generate_openrouter_profile(),
"requesty" => generate_requesty_profile(),
"github-copilot" => {
return Err("GitHub Copilot does not support API key authentication.\n\
It requires OAuth via your GitHub account.\n\n\
Expand All @@ -374,7 +375,7 @@ async fn handle_non_interactive_api_setup(
}
_ => {
return Err(format!(
"Unsupported provider '{}'. Supported: anthropic, openai, gemini, openrouter, stakpak, amazon-bedrock, github-copilot\n\
"Unsupported provider '{}'. Supported: anthropic, openai, gemini, openrouter, requesty, stakpak, amazon-bedrock, github-copilot\n\
For bedrock, use: stakpak auth login --provider amazon-bedrock --region <region>\n\
For github-copilot, run without --api-key to use the device flow.",
provider_id
Expand Down
4 changes: 3 additions & 1 deletion cli/src/config/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ impl AppConfig {
"openai" => "OPENAI_API_KEY",
"gemini" => "GEMINI_API_KEY",
"openrouter" => "OPENROUTER_API_KEY",
"requesty" => "REQUESTY_API_KEY",
_ => return None,
};

Expand Down Expand Up @@ -856,7 +857,7 @@ impl AppConfig {
}

let config_provider = Some("Local".to_string());
let builtin_providers = ["anthropic", "openai", "gemini", "openrouter"];
let builtin_providers = ["anthropic", "openai", "gemini", "openrouter", "requesty"];

for provider_name in builtin_providers {
if let Some(auth) = self.resolve_provider_auth(provider_name) {
Expand All @@ -865,6 +866,7 @@ impl AppConfig {
"openai" => "OpenAI",
"gemini" => "Gemini",
"openrouter" => "OpenRouter",
"requesty" => "Requesty",
_ => provider_name,
};

Expand Down
9 changes: 9 additions & 0 deletions cli/src/config/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ impl ProfileConfig {
}
}
}
ProviderConfig::Requesty { api_endpoint, .. } => {
if let Some(ep) = api_endpoint {
let clean = Self::clean_api_endpoint(Some(ep.clone()));
if clean.as_ref() != Some(ep) {
*api_endpoint = clean;
cleaned = true;
}
}
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion cli/src/onboarding/auth_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::onboarding::config_templates::{
BuiltinProvider, DEFAULT_MODEL, ProviderSetup, config_to_toml_preview,
generate_anthropic_profile, generate_gemini_profile, generate_github_copilot_profile,
generate_multi_provider_profile, generate_openai_profile, generate_openrouter_profile,
generate_requesty_profile,
};
use crate::onboarding::menu::{
prompt_password, prompt_yes_no, select_option, select_option_no_header,
Expand Down Expand Up @@ -355,6 +356,7 @@ async fn execute_hybrid_flow(config: &AuthFlowConfig) -> FlowOutcome {
(BuiltinProvider::OpenAI, "OpenAI", false),
(BuiltinProvider::Gemini, "Gemini", false),
(BuiltinProvider::OpenRouter, "OpenRouter", false),
(BuiltinProvider::Requesty, "Requesty", false),
];

loop {
Expand Down Expand Up @@ -518,6 +520,7 @@ fn render_default_model_for_provider(provider_id: &str) {
"openai" => "gpt-4.1",
"gemini" => "gemini-2.5-pro",
"openrouter" => "openrouter/anthropic/claude-sonnet-4",
"requesty" => "requesty/anthropic/claude-sonnet-4",
"github-copilot" => "github-copilot/gpt-4o",
_ => return,
};
Expand Down Expand Up @@ -584,7 +587,8 @@ fn provider_order_key(provider_id: &str, label: &str) -> (u8, String) {
"openai" => 1,
"gemini" => 2,
"openrouter" => 3,
"github-copilot" => 4,
"requesty" => 4,
"github-copilot" => 5,
_ => 100,
};
(priority, label.to_string())
Expand All @@ -596,6 +600,7 @@ fn build_provider_profile(provider_id: &str) -> Result<ProfileConfig, String> {
"openai" => Ok(generate_openai_profile()),
"gemini" => Ok(generate_gemini_profile()),
"openrouter" => Ok(generate_openrouter_profile()),
"requesty" => Ok(generate_requesty_profile()),
"github-copilot" => Ok(generate_github_copilot_profile()),
other => Err(format!("Unsupported provider for auth flow: {}", other)),
}
Expand Down Expand Up @@ -653,6 +658,7 @@ mod tests {
"OpenAI",
"Google (Gemini)",
"OpenRouter",
"Requesty",
"GitHub Copilot",
"Hybrid providers (e.g., Google and Anthropic)",
"Bring your own model",
Expand Down
47 changes: 47 additions & 0 deletions cli/src/onboarding/config_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,24 @@ pub fn generate_openrouter_profile() -> ProfileConfig {
profile
}

/// Generate Requesty profile configuration (credentials stored separately in config.toml auth field)
pub fn generate_requesty_profile() -> ProfileConfig {
let mut profile = ProfileConfig {
provider: Some(ProviderType::Local),
model: Some("requesty/anthropic/claude-sonnet-4".to_string()),
..ProfileConfig::default()
};
profile.providers.insert(
"requesty".to_string(),
ProviderConfig::Requesty {
api_key: None,
api_endpoint: None,
auth: None,
},
);
profile
}

/// Generate custom provider profile configuration
///
/// This creates a profile with a custom OpenAI-compatible provider (e.g., LiteLLM, Ollama).
Expand Down Expand Up @@ -200,6 +218,7 @@ pub enum BuiltinProvider {
Gemini,
Anthropic,
OpenRouter,
Requesty,
}

impl BuiltinProvider {
Expand All @@ -209,6 +228,7 @@ impl BuiltinProvider {
BuiltinProvider::Gemini => "Gemini",
BuiltinProvider::Anthropic => "Anthropic",
BuiltinProvider::OpenRouter => "OpenRouter",
BuiltinProvider::Requesty => "Requesty",
}
}

Expand All @@ -218,6 +238,7 @@ impl BuiltinProvider {
BuiltinProvider::Gemini => "gemini-2.5-pro",
BuiltinProvider::Anthropic => DEFAULT_MODEL,
BuiltinProvider::OpenRouter => "openrouter/anthropic/claude-sonnet-4",
BuiltinProvider::Requesty => "requesty/anthropic/claude-sonnet-4",
}
}
}
Expand Down Expand Up @@ -290,6 +311,16 @@ pub fn generate_multi_provider_profile(
},
);
}
BuiltinProvider::Requesty => {
profile.providers.insert(
"requesty".to_string(),
ProviderConfig::Requesty {
api_key: Some(setup.api_key),
api_endpoint: None,
auth: None,
},
);
}
}
}

Expand Down Expand Up @@ -442,6 +473,22 @@ pub fn config_to_toml_preview(profile: &ProfileConfig, profile_name: &str) -> St
));
}
}
ProviderConfig::Requesty {
api_key,
api_endpoint,
..
} => {
toml.push_str("type = \"requesty\"\n");
if let Some(endpoint) = api_endpoint {
toml.push_str(&format!("api_endpoint = \"{}\"\n", endpoint));
}
if let Some(key) = api_key {
toml.push_str(&format!(
"api_key = \"{}\"\n",
if key.is_empty() { "" } else { "***" }
));
}
}
}
}

Expand Down
9 changes: 8 additions & 1 deletion libs/ai/src/client/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::error::Result;
use crate::provider::Provider;
use crate::providers::{
anthropic::AnthropicProvider, gemini::GeminiProvider, openai::OpenAIProvider,
openrouter::OpenRouterProvider, stakpak::StakpakProvider,
openrouter::OpenRouterProvider, requesty::RequestyProvider, stakpak::StakpakProvider,
};
use crate::registry::ProviderRegistry;

Expand Down Expand Up @@ -79,6 +79,13 @@ impl ClientBuilder {
registry = registry.register("openrouter", provider);
}

// Register Requesty if configured
if let Some(config) = inference_config.requesty_config
&& let Ok(provider) = RequestyProvider::new(config)
{
registry = registry.register("requesty", provider);
}

// Register Bedrock if configured
#[cfg(feature = "bedrock")]
if let Some(config) = inference_config.bedrock_config {
Expand Down
44 changes: 43 additions & 1 deletion libs/ai/src/client/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::providers::{
anthropic::AnthropicConfig, gemini::GeminiConfig, openai::OpenAIConfig,
openrouter::OpenRouterConfig, stakpak::StakpakProviderConfig,
openrouter::OpenRouterConfig, requesty::RequestyConfig, stakpak::StakpakProviderConfig,
};

#[cfg(feature = "bedrock")]
Expand Down Expand Up @@ -68,6 +68,7 @@ pub struct InferenceConfig {
pub(crate) gemini_config: Option<GeminiConfig>,
pub(crate) stakpak_config: Option<StakpakProviderConfig>,
pub(crate) openrouter_config: Option<OpenRouterConfig>,
pub(crate) requesty_config: Option<RequestyConfig>,
#[cfg(feature = "bedrock")]
pub(crate) bedrock_config: Option<BedrockConfig>,
pub(crate) client_config: ClientConfig,
Expand Down Expand Up @@ -299,6 +300,47 @@ impl InferenceConfig {
self
}

/// Configure Requesty provider with API key and optional base URL
///
/// # Example
///
/// ```rust,no_run
/// # use stakai::InferenceConfig;
/// let config = InferenceConfig::new()
/// .requesty("sk-...", None);
///
/// // With custom base URL
/// let config = InferenceConfig::new()
/// .requesty("sk-...", Some("https://custom.requesty.ai/v1".to_string()));
/// ```
pub fn requesty(mut self, api_key: impl Into<String>, base_url: Option<String>) -> Self {
let mut config = RequestyConfig::new(api_key);
if let Some(url) = base_url {
config = config.with_base_url(url);
}
self.requesty_config = Some(config);
self
}

/// Configure Requesty provider with full RequestyConfig
///
/// # Example
///
/// ```rust,no_run
/// # use stakai::{InferenceConfig, providers::requesty::RequestyConfig};
/// let requesty_config = RequestyConfig::new("sk-...")
/// .with_base_url("https://custom.requesty.ai/v1")
/// .with_http_referer("https://example.com")
/// .with_site_title("My App");
///
/// let config = InferenceConfig::new()
/// .requesty_config(requesty_config);
/// ```
pub fn requesty_config(mut self, config: RequestyConfig) -> Self {
self.requesty_config = Some(config);
self
}

/// Configure AWS Bedrock provider with a region
///
/// Uses the AWS credential chain for authentication (env vars, shared credentials,
Expand Down
2 changes: 2 additions & 0 deletions libs/ai/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod copilot;
pub mod gemini;
pub mod openai;
pub mod openrouter;
pub mod requesty;
pub mod stakpak;
pub(crate) mod tls;

Expand All @@ -18,4 +19,5 @@ pub use copilot::{CopilotConfig, CopilotProvider};
pub use gemini::GeminiProvider;
pub use openai::OpenAIProvider;
pub use openrouter::{OpenRouterConfig, OpenRouterProvider};
pub use requesty::{RequestyConfig, RequestyProvider};
pub use stakpak::StakpakProvider;
7 changes: 7 additions & 0 deletions libs/ai/src/providers/requesty/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Requesty provider

mod provider;
pub mod types;

pub use provider::RequestyProvider;
pub use types::RequestyConfig;
Loading