mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Custom providers update (#4099)
Co-authored-by: developerayo <shodipovi@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Zane Staggs <zane@squareup.com>
This commit is contained in:
Generated
+2
@@ -2687,6 +2687,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"webbrowser 1.0.4",
|
||||
"winapi",
|
||||
]
|
||||
@@ -2774,6 +2775,7 @@ dependencies = [
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -47,6 +47,7 @@ shlex = "1.3.0"
|
||||
async-trait = "0.1.86"
|
||||
base64 = "0.22.1"
|
||||
regex = "1.11.1"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
nix = { version = "0.30.1", features = ["process", "signal"] }
|
||||
tar = "0.4"
|
||||
# Web server dependencies
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
use cliclack::spinner;
|
||||
use console::style;
|
||||
use goose::agents::extension::ToolInfo;
|
||||
@@ -7,6 +8,7 @@ use goose::agents::platform_tools::{
|
||||
};
|
||||
use goose::agents::Agent;
|
||||
use goose::agents::{extension::Envs, ExtensionConfig};
|
||||
use goose::config::custom_providers::CustomProviderConfig;
|
||||
use goose::config::extensions::name_to_key;
|
||||
use goose::config::permission::PermissionLevel;
|
||||
use goose::config::{
|
||||
@@ -14,6 +16,7 @@ use goose::config::{
|
||||
PermissionManager,
|
||||
};
|
||||
use goose::conversation::message::Message;
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::{create, providers};
|
||||
use rmcp::model::{Tool, ToolAnnotations};
|
||||
use rmcp::object;
|
||||
@@ -21,8 +24,6 @@ use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
|
||||
// useful for light themes where there is no dicernible colour contrast between
|
||||
// cursor-selected and cursor-unselected items.
|
||||
const MULTISELECT_VISIBILITY_HINT: &str = "<";
|
||||
@@ -221,6 +222,11 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
"Configure Providers",
|
||||
"Change provider or update credentials",
|
||||
)
|
||||
.item(
|
||||
"custom_providers",
|
||||
"Custom Providers",
|
||||
"Add custom provider with compatible API",
|
||||
)
|
||||
.item("add", "Add Extension", "Connect to a new extension")
|
||||
.item(
|
||||
"toggle",
|
||||
@@ -241,6 +247,7 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
"remove" => remove_extension_dialog(),
|
||||
"settings" => configure_settings_dialog().await.and(Ok(())),
|
||||
"providers" => configure_provider_dialog().await.and(Ok(())),
|
||||
"custom_providers" => configure_custom_provider_dialog(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -250,10 +257,7 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
async fn handle_oauth_configuration(
|
||||
provider_name: &str,
|
||||
key_name: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::create;
|
||||
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = cliclack::log::info(format!(
|
||||
"Configuring {} using OAuth device code flow...",
|
||||
key_name
|
||||
@@ -279,8 +283,7 @@ async fn handle_oauth_configuration(
|
||||
}
|
||||
}
|
||||
|
||||
/// Interactive model search that truncates the list to improve UX
|
||||
fn interactive_model_search(models: &[String]) -> Result<String, Box<dyn std::error::Error>> {
|
||||
fn interactive_model_search(models: &[String]) -> Result<String, Box<dyn Error>> {
|
||||
const MAX_VISIBLE: usize = 30;
|
||||
let mut query = String::new();
|
||||
|
||||
@@ -553,7 +556,7 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
|
||||
let spin = spinner();
|
||||
spin.start("Attempting to fetch supported models...");
|
||||
let models_res = {
|
||||
let temp_model_config = goose::model::ModelConfig::new(&provider_meta.default_model)?;
|
||||
let temp_model_config = ModelConfig::new(&provider_meta.default_model)?;
|
||||
let temp_provider = create(provider_name, temp_model_config)?;
|
||||
temp_provider.fetch_supported_models().await
|
||||
};
|
||||
@@ -585,7 +588,7 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
|
||||
.map(|val| val == "1" || val.to_lowercase() == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
let model_config = goose::model::ModelConfig::new(&model)?
|
||||
let model_config = ModelConfig::new(&model)?
|
||||
.with_max_tokens(Some(50))
|
||||
.with_toolshim(toolshim_enabled)
|
||||
.with_toolshim_model(std::env::var("GOOSE_TOOLSHIM_OLLAMA_MODEL").ok());
|
||||
@@ -1429,7 +1432,7 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box<dyn Error>> {
|
||||
let model: String = config
|
||||
.get_param("GOOSE_MODEL")
|
||||
.expect("No model configured. Please set model first");
|
||||
let model_config = goose::model::ModelConfig::new(&model)?;
|
||||
let model_config = ModelConfig::new(&model)?;
|
||||
|
||||
// Create the agent
|
||||
let agent = Agent::new();
|
||||
@@ -1569,7 +1572,6 @@ fn configure_recipe_dialog() -> Result<(), Box<dyn Error>> {
|
||||
recipe_repo_input = recipe_repo_input.default_input(&recipe_repo);
|
||||
}
|
||||
let input_value: String = recipe_repo_input.interact()?;
|
||||
// if input is blank, it clears the recipe github repo settings in the config file
|
||||
if input_value.clone().trim().is_empty() {
|
||||
config.delete(key_name)?;
|
||||
} else {
|
||||
@@ -1767,3 +1769,128 @@ pub async fn handle_openrouter_auth() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_provider() -> Result<(), Box<dyn Error>> {
|
||||
let provider_type = cliclack::select("What type of API is this?")
|
||||
.item(
|
||||
"openai_compatible",
|
||||
"OpenAI Compatible",
|
||||
"Uses OpenAI API format",
|
||||
)
|
||||
.item(
|
||||
"anthropic_compatible",
|
||||
"Anthropic Compatible",
|
||||
"Uses Anthropic API format",
|
||||
)
|
||||
.item(
|
||||
"ollama_compatible",
|
||||
"Ollama Compatible",
|
||||
"Uses Ollama API format",
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
let display_name: String = cliclack::input("What should we call this provider?")
|
||||
.placeholder("Your Provider Name")
|
||||
.validate(|input: &String| {
|
||||
if input.is_empty() {
|
||||
Err("Please enter a name")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let api_url: String = cliclack::input("Provider API URL:")
|
||||
.placeholder("https://api.example.com/v1/messages")
|
||||
.validate(|input: &String| {
|
||||
if !input.starts_with("http://") && !input.starts_with("https://") {
|
||||
Err("URL must start with either http:// or https://")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let api_key: String = cliclack::password("API key:").mask('▪').interact()?;
|
||||
|
||||
let models_input: String = cliclack::input("Available models (seperate with commas):")
|
||||
.placeholder("model-a, model-b, model-c")
|
||||
.validate(|input: &String| {
|
||||
if input.trim().is_empty() {
|
||||
Err("Please enter at least one model name")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let models: Vec<String> = models_input
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
let supports_streaming = cliclack::confirm("Does this provider support streaming responses?")
|
||||
.initial_value(true)
|
||||
.interact()?;
|
||||
|
||||
CustomProviderConfig::create_and_save(
|
||||
provider_type,
|
||||
display_name.clone(),
|
||||
api_url,
|
||||
api_key,
|
||||
models,
|
||||
Some(supports_streaming),
|
||||
)?;
|
||||
|
||||
cliclack::outro(format!("Custom provider added: {}", display_name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_provider() -> Result<(), Box<dyn Error>> {
|
||||
let custom_providers_dir = goose::config::custom_providers::custom_providers_dir();
|
||||
let custom_providers = if custom_providers_dir.exists() {
|
||||
goose::config::custom_providers::load_custom_providers(&custom_providers_dir)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
if custom_providers.is_empty() {
|
||||
cliclack::outro("No custom providers added just yet.")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let provider_items: Vec<_> = custom_providers
|
||||
.iter()
|
||||
.map(|p| (p.name.as_str(), p.display_name.as_str(), "Custom provider"))
|
||||
.collect();
|
||||
|
||||
let selected_id = cliclack::select("Which custom provider would you like to remove?")
|
||||
.items(&provider_items)
|
||||
.interact()?;
|
||||
|
||||
CustomProviderConfig::remove(selected_id)?;
|
||||
cliclack::outro(format!("Removed custom provider: {}", selected_id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure_custom_provider_dialog() -> Result<(), Box<dyn Error>> {
|
||||
let action = cliclack::select("What would you like to do?")
|
||||
.item(
|
||||
"add",
|
||||
"Add A Custom Provider",
|
||||
"Add a new OpenAI/Anthropic/Ollama compatible Provider",
|
||||
)
|
||||
.item(
|
||||
"remove",
|
||||
"Remove Custom Provider",
|
||||
"Remove an existing custom provider",
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
match action {
|
||||
"add" => add_provider(),
|
||||
"remove" => remove_provider(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ serde_yaml = "0.9.34"
|
||||
utoipa = { version = "4.1", features = ["axum_extras", "chrono"] }
|
||||
reqwest = { version = "0.12.9", features = ["json", "rustls-tls", "blocking", "multipart"], default-features = false }
|
||||
tokio-util = "0.7.15"
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
|
||||
[[bin]]
|
||||
name = "goosed"
|
||||
|
||||
@@ -367,6 +367,8 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema {
|
||||
super::routes::config_management::read_all_config,
|
||||
super::routes::config_management::providers,
|
||||
super::routes::config_management::upsert_permissions,
|
||||
super::routes::config_management::create_custom_provider,
|
||||
super::routes::config_management::remove_custom_provider,
|
||||
super::routes::agent::get_tools,
|
||||
super::routes::agent::add_sub_recipes,
|
||||
super::routes::agent::extend_prompt,
|
||||
@@ -402,6 +404,7 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema {
|
||||
super::routes::config_management::ExtensionQuery,
|
||||
super::routes::config_management::ToolPermission,
|
||||
super::routes::config_management::UpsertPermissionsQuery,
|
||||
super::routes::config_management::CreateCustomProviderRequest,
|
||||
super::routes::reply::PermissionConfirmationRequest,
|
||||
super::routes::context::ContextManageRequest,
|
||||
super::routes::context::ContextManageResponse,
|
||||
|
||||
@@ -192,7 +192,7 @@ async fn update_agent_provider(
|
||||
let agent = state
|
||||
.get_agent()
|
||||
.await
|
||||
.map_err(|_| StatusCode::PRECONDITION_FAILED)?;
|
||||
.map_err(|_e| StatusCode::PRECONDITION_FAILED)?;
|
||||
|
||||
let config = Config::global();
|
||||
let model = match payload
|
||||
@@ -210,7 +210,7 @@ async fn update_agent_provider(
|
||||
agent
|
||||
.update_provider(new_provider)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
.map_err(|_e| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use axum::{
|
||||
};
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use goose::config::APP_STRATEGY;
|
||||
use goose::config::{extensions::name_to_key, PermissionManager};
|
||||
use goose::config::{Config, ConfigError};
|
||||
use goose::config::{ExtensionConfigManager, ExtensionEntry};
|
||||
use goose::model::ModelConfig;
|
||||
@@ -78,6 +77,16 @@ pub struct UpsertPermissionsQuery {
|
||||
pub tool_permissions: Vec<ToolPermission>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateCustomProviderRequest {
|
||||
pub provider_type: String,
|
||||
pub display_name: String,
|
||||
pub api_url: String,
|
||||
pub api_key: String,
|
||||
pub models: Vec<String>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/config/upsert",
|
||||
@@ -227,7 +236,7 @@ pub async fn add_extension(
|
||||
|
||||
let extensions =
|
||||
ExtensionConfigManager::get_all().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let key = name_to_key(&extension_query.name);
|
||||
let key = goose::config::extensions::name_to_key(&extension_query.name);
|
||||
|
||||
let is_update = extensions.iter().any(|e| e.config.key() == key);
|
||||
|
||||
@@ -262,7 +271,7 @@ pub async fn remove_extension(
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let key = name_to_key(&name);
|
||||
let key = goose::config::extensions::name_to_key(&name);
|
||||
match ExtensionConfigManager::remove(&key) {
|
||||
Ok(_) => Ok(Json(format!("Removed extension {}", name))),
|
||||
Err(_) => Err(StatusCode::NOT_FOUND),
|
||||
@@ -304,7 +313,62 @@ pub async fn providers(
|
||||
) -> Result<Json<Vec<ProviderDetails>>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let providers_metadata = get_providers();
|
||||
let mut providers_metadata = get_providers();
|
||||
|
||||
let custom_providers_dir = goose::config::custom_providers::custom_providers_dir();
|
||||
|
||||
if custom_providers_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(&custom_providers_dir) {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(extension) = entry.path().extension() {
|
||||
if extension == "json" {
|
||||
if let Ok(content) = std::fs::read_to_string(entry.path()) {
|
||||
if let Ok(custom_provider) = serde_json::from_str::<
|
||||
goose::config::custom_providers::CustomProviderConfig,
|
||||
>(&content)
|
||||
{
|
||||
// CustomProviderConfig => ProviderMetadata
|
||||
let default_model = custom_provider
|
||||
.models
|
||||
.first()
|
||||
.map(|m| m.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let metadata = goose::providers::base::ProviderMetadata {
|
||||
name: custom_provider.name.clone(),
|
||||
display_name: custom_provider.display_name.clone(),
|
||||
description: custom_provider
|
||||
.description
|
||||
.clone()
|
||||
.unwrap_or_else(|| {
|
||||
format!("{} (custom)", custom_provider.display_name)
|
||||
}),
|
||||
default_model,
|
||||
known_models: custom_provider.models.clone(),
|
||||
model_doc_link: "Custom provider".to_string(),
|
||||
config_keys: vec![
|
||||
goose::providers::base::ConfigKey::new(
|
||||
&custom_provider.api_key_env,
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
),
|
||||
goose::providers::base::ConfigKey::new(
|
||||
"CUSTOM_PROVIDER_BASE_URL",
|
||||
true,
|
||||
false,
|
||||
Some(&custom_provider.base_url),
|
||||
),
|
||||
],
|
||||
};
|
||||
providers_metadata.push(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let providers_response: Vec<ProviderDetails> = providers_metadata
|
||||
.into_iter()
|
||||
@@ -491,7 +555,7 @@ pub async fn upsert_permissions(
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let mut permission_manager = PermissionManager::default();
|
||||
let mut permission_manager = goose::config::PermissionManager::default();
|
||||
|
||||
for tool_permission in &query.tool_permissions {
|
||||
permission_manager.update_user_permission(
|
||||
@@ -637,6 +701,66 @@ pub async fn get_current_model(
|
||||
})))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/config/custom-providers",
|
||||
request_body = CreateCustomProviderRequest,
|
||||
responses(
|
||||
(status = 200, description = "Custom provider created successfully", body = String),
|
||||
(status = 400, description = "Invalid request"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn create_custom_provider(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Json(request): Json<CreateCustomProviderRequest>,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
let config = goose::config::custom_providers::CustomProviderConfig::create_and_save(
|
||||
&request.provider_type,
|
||||
request.display_name,
|
||||
request.api_url,
|
||||
request.api_key,
|
||||
request.models,
|
||||
request.supports_streaming,
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
if let Err(e) = goose::providers::refresh_custom_providers() {
|
||||
tracing::warn!("Failed to refresh custom providers after creation: {}", e);
|
||||
}
|
||||
|
||||
Ok(Json(format!("Custom provider added - ID: {}", config.id())))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/config/custom-providers/{id}",
|
||||
responses(
|
||||
(status = 200, description = "Custom provider removed successfully", body = String),
|
||||
(status = 404, description = "Provider not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
)
|
||||
)]
|
||||
pub async fn remove_custom_provider(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
axum::extract::Path(id): axum::extract::Path<String>,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
|
||||
goose::config::custom_providers::CustomProviderConfig::remove(&id)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
if let Err(e) = goose::providers::refresh_custom_providers() {
|
||||
tracing::warn!("Failed to refresh custom providers after deletion: {}", e);
|
||||
}
|
||||
|
||||
Ok(Json(format!("Removed custom provider: {}", id)))
|
||||
}
|
||||
|
||||
pub fn routes(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/config", get(read_all_config))
|
||||
@@ -654,6 +778,11 @@ pub fn routes(state: Arc<AppState>) -> Router {
|
||||
.route("/config/validate", get(validate_config))
|
||||
.route("/config/permissions", post(upsert_permissions))
|
||||
.route("/config/current-model", get(get_current_model))
|
||||
.route("/config/custom-providers", post(create_custom_provider))
|
||||
.route(
|
||||
"/config/custom-providers/{id}",
|
||||
delete(remove_custom_provider),
|
||||
)
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
use crate::config::{Config, APP_STRATEGY};
|
||||
use crate::model::ModelConfig;
|
||||
use crate::providers::anthropic::AnthropicProvider;
|
||||
use crate::providers::base::ModelInfo;
|
||||
use crate::providers::ollama::OllamaProvider;
|
||||
use crate::providers::openai::OpenAiProvider;
|
||||
use anyhow::Result;
|
||||
use etcetera::{choose_app_strategy, AppStrategy};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn custom_providers_dir() -> std::path::PathBuf {
|
||||
choose_app_strategy(APP_STRATEGY.clone())
|
||||
.expect("goose requires a home dir")
|
||||
.config_dir()
|
||||
.join("custom_providers")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProviderEngine {
|
||||
OpenAI,
|
||||
Ollama,
|
||||
Anthropic,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomProviderConfig {
|
||||
pub name: String,
|
||||
pub engine: ProviderEngine,
|
||||
pub display_name: String,
|
||||
pub description: Option<String>,
|
||||
pub api_key_env: String,
|
||||
pub base_url: String,
|
||||
pub models: Vec<ModelInfo>,
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
pub timeout_seconds: Option<u64>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
}
|
||||
|
||||
impl CustomProviderConfig {
|
||||
pub fn id(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
&self.display_name
|
||||
}
|
||||
|
||||
pub fn models(&self) -> &[ModelInfo] {
|
||||
&self.models
|
||||
}
|
||||
|
||||
pub fn generate_id(display_name: &str) -> String {
|
||||
format!("custom_{}", display_name.to_lowercase().replace(' ', "_"))
|
||||
}
|
||||
|
||||
pub fn generate_api_key_name(id: &str) -> String {
|
||||
format!("{}_API_KEY", id.to_uppercase())
|
||||
}
|
||||
|
||||
pub fn create_and_save(
|
||||
provider_type: &str,
|
||||
display_name: String,
|
||||
api_url: String,
|
||||
api_key: String,
|
||||
models: Vec<String>,
|
||||
supports_streaming: Option<bool>,
|
||||
) -> Result<Self> {
|
||||
let id = Self::generate_id(&display_name);
|
||||
let api_key_name = Self::generate_api_key_name(&id);
|
||||
|
||||
let config = Config::global();
|
||||
config.set_secret(&api_key_name, serde_json::Value::String(api_key))?;
|
||||
|
||||
let model_infos: Vec<ModelInfo> = models
|
||||
.into_iter()
|
||||
.map(|name| ModelInfo::new(name, 128000))
|
||||
.collect();
|
||||
|
||||
let provider_config = CustomProviderConfig {
|
||||
name: id.clone(),
|
||||
engine: match provider_type {
|
||||
"openai_compatible" => ProviderEngine::OpenAI,
|
||||
"anthropic_compatible" => ProviderEngine::Anthropic,
|
||||
"ollama_compatible" => ProviderEngine::Ollama,
|
||||
_ => return Err(anyhow::anyhow!("Invalid provider type: {}", provider_type)),
|
||||
},
|
||||
display_name: display_name.clone(),
|
||||
description: Some(format!("Custom {} provider", display_name)),
|
||||
api_key_env: api_key_name,
|
||||
base_url: api_url,
|
||||
models: model_infos,
|
||||
headers: None,
|
||||
timeout_seconds: None,
|
||||
supports_streaming,
|
||||
};
|
||||
|
||||
// save to JSON file
|
||||
let custom_providers_dir = custom_providers_dir();
|
||||
std::fs::create_dir_all(&custom_providers_dir)?;
|
||||
|
||||
let json_content = serde_json::to_string_pretty(&provider_config)?;
|
||||
let file_path = custom_providers_dir.join(format!("{}.json", id));
|
||||
std::fs::write(file_path, json_content)?;
|
||||
|
||||
Ok(provider_config)
|
||||
}
|
||||
|
||||
pub fn remove(id: &str) -> Result<()> {
|
||||
let config = Config::global();
|
||||
let api_key_name = Self::generate_api_key_name(id);
|
||||
let _ = config.delete_secret(&api_key_name);
|
||||
|
||||
let custom_providers_dir = custom_providers_dir();
|
||||
let file_path = custom_providers_dir.join(format!("{}.json", id));
|
||||
|
||||
if file_path.exists() {
|
||||
std::fs::remove_file(file_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_custom_providers(dir: &Path) -> Result<Vec<CustomProviderConfig>> {
|
||||
if !dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
std::fs::read_dir(dir)?
|
||||
.filter_map(|entry| {
|
||||
let path = entry.ok()?.path();
|
||||
(path.extension()? == "json").then_some(path)
|
||||
})
|
||||
.map(|path| {
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse {}: {}", path.display(), e))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn register_custom_providers(
|
||||
registry: &mut crate::providers::provider_registry::ProviderRegistry,
|
||||
dir: &Path,
|
||||
) -> Result<()> {
|
||||
let configs = load_custom_providers(dir)?;
|
||||
|
||||
for config in configs {
|
||||
let config_clone = config.clone();
|
||||
let description = config
|
||||
.description
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("Custom {} provider", config.display_name));
|
||||
let default_model = config
|
||||
.models
|
||||
.first()
|
||||
.map(|m| m.name.clone())
|
||||
.unwrap_or_default();
|
||||
let known_models: Vec<ModelInfo> = config
|
||||
.models
|
||||
.iter()
|
||||
.map(|m| ModelInfo {
|
||||
name: m.name.clone(),
|
||||
context_limit: m.context_limit,
|
||||
input_token_cost: m.input_token_cost,
|
||||
output_token_cost: m.output_token_cost,
|
||||
currency: m.currency.clone(),
|
||||
supports_cache_control: Some(m.supports_cache_control.unwrap_or(false)),
|
||||
})
|
||||
.collect();
|
||||
|
||||
match config.engine {
|
||||
ProviderEngine::OpenAI => {
|
||||
registry.register_with_name::<OpenAiProvider, _>(
|
||||
config.name.clone(),
|
||||
config.display_name.clone(),
|
||||
description,
|
||||
default_model,
|
||||
known_models,
|
||||
move |model: ModelConfig| {
|
||||
OpenAiProvider::from_custom_config(model, config_clone.clone())
|
||||
},
|
||||
);
|
||||
}
|
||||
ProviderEngine::Ollama => {
|
||||
registry.register_with_name::<OllamaProvider, _>(
|
||||
config.name.clone(),
|
||||
config.display_name.clone(),
|
||||
description,
|
||||
default_model,
|
||||
known_models,
|
||||
move |model: ModelConfig| {
|
||||
OllamaProvider::from_custom_config(model, config_clone.clone())
|
||||
},
|
||||
);
|
||||
}
|
||||
ProviderEngine::Anthropic => {
|
||||
registry.register_with_name::<AnthropicProvider, _>(
|
||||
config.name.clone(),
|
||||
config.display_name.clone(),
|
||||
description,
|
||||
default_model,
|
||||
known_models,
|
||||
move |model: ModelConfig| {
|
||||
AnthropicProvider::from_custom_config(model, config_clone.clone())
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod base;
|
||||
pub mod custom_providers;
|
||||
mod experiments;
|
||||
pub mod extensions;
|
||||
pub mod permission;
|
||||
@@ -6,6 +7,7 @@ pub mod signup_openrouter;
|
||||
|
||||
pub use crate::agents::ExtensionConfig;
|
||||
pub use base::{Config, ConfigError, APP_STRATEGY};
|
||||
pub use custom_providers::CustomProviderConfig;
|
||||
pub use experiments::ExperimentManager;
|
||||
pub use extensions::{ExtensionConfigManager, ExtensionEntry};
|
||||
pub use permission::PermissionManager;
|
||||
|
||||
@@ -15,6 +15,7 @@ use super::formats::anthropic::{
|
||||
create_request, get_usage, response_to_message, response_to_streaming_message,
|
||||
};
|
||||
use super::utils::{emit_debug_trace, get_model, map_http_error_to_provider_error};
|
||||
use crate::config::custom_providers::CustomProviderConfig;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::impl_provider_default;
|
||||
use crate::model::ModelConfig;
|
||||
@@ -42,6 +43,7 @@ pub struct AnthropicProvider {
|
||||
#[serde(skip)]
|
||||
api_client: ApiClient,
|
||||
model: ModelConfig,
|
||||
supports_streaming: bool,
|
||||
}
|
||||
|
||||
impl_provider_default!(AnthropicProvider);
|
||||
@@ -62,7 +64,32 @@ impl AnthropicProvider {
|
||||
let api_client =
|
||||
ApiClient::new(host, auth)?.with_header("anthropic-version", ANTHROPIC_API_VERSION)?;
|
||||
|
||||
Ok(Self { api_client, model })
|
||||
Ok(Self {
|
||||
api_client,
|
||||
model,
|
||||
supports_streaming: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_custom_config(model: ModelConfig, config: CustomProviderConfig) -> Result<Self> {
|
||||
let global_config = crate::config::Config::global();
|
||||
let api_key: String = global_config
|
||||
.get_secret(&config.api_key_env)
|
||||
.map_err(|_| anyhow::anyhow!("Missing API key: {}", config.api_key_env))?;
|
||||
|
||||
let auth = AuthMethod::ApiKey {
|
||||
header_name: "x-api-key".to_string(),
|
||||
key: api_key,
|
||||
};
|
||||
|
||||
let api_client = ApiClient::new(config.base_url, auth)?
|
||||
.with_header("anthropic-version", ANTHROPIC_API_VERSION)?;
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
model,
|
||||
supports_streaming: config.supports_streaming.unwrap_or(true),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_conditional_headers(&self) -> Vec<(&str, &str)> {
|
||||
@@ -260,6 +287,6 @@ impl Provider for AnthropicProvider {
|
||||
}
|
||||
|
||||
fn supports_streaming(&self) -> bool {
|
||||
true
|
||||
self.supports_streaming
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use super::{
|
||||
anthropic::AnthropicProvider,
|
||||
@@ -17,66 +17,87 @@ use super::{
|
||||
ollama::OllamaProvider,
|
||||
openai::OpenAiProvider,
|
||||
openrouter::OpenRouterProvider,
|
||||
provider_registry::ProviderRegistry,
|
||||
sagemaker_tgi::SageMakerTgiProvider,
|
||||
snowflake::SnowflakeProvider,
|
||||
venice::VeniceProvider,
|
||||
xai::XaiProvider,
|
||||
};
|
||||
use crate::config::custom_providers::{custom_providers_dir, register_custom_providers};
|
||||
use crate::model::ModelConfig;
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::errors::ProviderError;
|
||||
#[cfg(test)]
|
||||
use rmcp::model::Tool;
|
||||
|
||||
fn default_lead_turns() -> usize {
|
||||
3
|
||||
}
|
||||
fn default_failure_threshold() -> usize {
|
||||
2
|
||||
}
|
||||
fn default_fallback_turns() -> usize {
|
||||
2
|
||||
const DEFAULT_LEAD_TURNS: usize = 3;
|
||||
const DEFAULT_FAILURE_THRESHOLD: usize = 2;
|
||||
const DEFAULT_FALLBACK_TURNS: usize = 2;
|
||||
|
||||
static REGISTRY: Lazy<RwLock<ProviderRegistry>> = Lazy::new(|| {
|
||||
let registry = ProviderRegistry::new().with_providers(|registry| {
|
||||
registry.register::<AnthropicProvider, _>(AnthropicProvider::from_env);
|
||||
registry.register::<AzureProvider, _>(AzureProvider::from_env);
|
||||
registry.register::<BedrockProvider, _>(BedrockProvider::from_env);
|
||||
registry.register::<ClaudeCodeProvider, _>(ClaudeCodeProvider::from_env);
|
||||
registry.register::<CursorAgentProvider, _>(CursorAgentProvider::from_env);
|
||||
registry.register::<DatabricksProvider, _>(DatabricksProvider::from_env);
|
||||
registry.register::<GcpVertexAIProvider, _>(GcpVertexAIProvider::from_env);
|
||||
registry.register::<GeminiCliProvider, _>(GeminiCliProvider::from_env);
|
||||
registry.register::<GoogleProvider, _>(GoogleProvider::from_env);
|
||||
registry.register::<GroqProvider, _>(GroqProvider::from_env);
|
||||
registry.register::<LiteLLMProvider, _>(LiteLLMProvider::from_env);
|
||||
registry.register::<OllamaProvider, _>(OllamaProvider::from_env);
|
||||
registry.register::<OpenAiProvider, _>(OpenAiProvider::from_env);
|
||||
registry.register::<OpenRouterProvider, _>(OpenRouterProvider::from_env);
|
||||
registry.register::<SageMakerTgiProvider, _>(SageMakerTgiProvider::from_env);
|
||||
registry.register::<SnowflakeProvider, _>(SnowflakeProvider::from_env);
|
||||
registry.register::<VeniceProvider, _>(VeniceProvider::from_env);
|
||||
registry.register::<XaiProvider, _>(XaiProvider::from_env);
|
||||
|
||||
if let Err(e) = load_custom_providers_into_registry(registry) {
|
||||
tracing::warn!("Failed to load custom providers: {}", e);
|
||||
}
|
||||
});
|
||||
RwLock::new(registry)
|
||||
});
|
||||
|
||||
fn load_custom_providers_into_registry(registry: &mut ProviderRegistry) -> Result<()> {
|
||||
let config_dir = custom_providers_dir();
|
||||
register_custom_providers(registry, &config_dir)
|
||||
}
|
||||
|
||||
pub fn providers() -> Vec<ProviderMetadata> {
|
||||
vec![
|
||||
AnthropicProvider::metadata(),
|
||||
AzureProvider::metadata(),
|
||||
BedrockProvider::metadata(),
|
||||
ClaudeCodeProvider::metadata(),
|
||||
CursorAgentProvider::metadata(),
|
||||
DatabricksProvider::metadata(),
|
||||
GcpVertexAIProvider::metadata(),
|
||||
GeminiCliProvider::metadata(),
|
||||
// GithubCopilotProvider::metadata(),
|
||||
GoogleProvider::metadata(),
|
||||
GroqProvider::metadata(),
|
||||
LiteLLMProvider::metadata(),
|
||||
OllamaProvider::metadata(),
|
||||
OpenAiProvider::metadata(),
|
||||
OpenRouterProvider::metadata(),
|
||||
SageMakerTgiProvider::metadata(),
|
||||
VeniceProvider::metadata(),
|
||||
SnowflakeProvider::metadata(),
|
||||
XaiProvider::metadata(),
|
||||
]
|
||||
REGISTRY.read().unwrap().all_metadata()
|
||||
}
|
||||
|
||||
pub fn refresh_custom_providers() -> Result<()> {
|
||||
let mut registry = REGISTRY.write().unwrap();
|
||||
registry.remove_custom_providers();
|
||||
|
||||
if let Err(e) = load_custom_providers_into_registry(&mut registry) {
|
||||
tracing::warn!("Failed to refresh custom providers: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
tracing::info!("Custom providers refreshed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create(name: &str, model: ModelConfig) -> Result<Arc<dyn Provider>> {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
// Check for lead model environment variables
|
||||
if let Ok(lead_model_name) = config.get_param::<String>("GOOSE_LEAD_MODEL") {
|
||||
tracing::info!("Creating lead/worker provider from environment variables");
|
||||
|
||||
return create_lead_worker_from_env(name, &model, &lead_model_name);
|
||||
}
|
||||
create_provider(name, model)
|
||||
|
||||
REGISTRY.read().unwrap().create(name, model)
|
||||
}
|
||||
|
||||
/// Create a lead/worker provider from environment variables
|
||||
fn create_lead_worker_from_env(
|
||||
default_provider_name: &str,
|
||||
default_model: &ModelConfig,
|
||||
@@ -84,61 +105,36 @@ fn create_lead_worker_from_env(
|
||||
) -> Result<Arc<dyn Provider>> {
|
||||
let config = crate::config::Config::global();
|
||||
|
||||
// Get lead provider (optional, defaults to main provider)
|
||||
let lead_provider_name = config
|
||||
.get_param::<String>("GOOSE_LEAD_PROVIDER")
|
||||
.unwrap_or_else(|_| default_provider_name.to_string());
|
||||
|
||||
// Get configuration parameters with defaults
|
||||
let lead_turns = config
|
||||
.get_param::<usize>("GOOSE_LEAD_TURNS")
|
||||
.unwrap_or(default_lead_turns());
|
||||
.unwrap_or(DEFAULT_LEAD_TURNS);
|
||||
let failure_threshold = config
|
||||
.get_param::<usize>("GOOSE_LEAD_FAILURE_THRESHOLD")
|
||||
.unwrap_or(default_failure_threshold());
|
||||
.unwrap_or(DEFAULT_FAILURE_THRESHOLD);
|
||||
let fallback_turns = config
|
||||
.get_param::<usize>("GOOSE_LEAD_FALLBACK_TURNS")
|
||||
.unwrap_or(default_fallback_turns());
|
||||
.unwrap_or(DEFAULT_FALLBACK_TURNS);
|
||||
|
||||
let lead_model_config = ModelConfig::new_with_context_env(
|
||||
lead_model_name.to_string(),
|
||||
Some("GOOSE_LEAD_CONTEXT_LIMIT"),
|
||||
)?;
|
||||
|
||||
// For worker model, preserve the original context_limit from config (highest precedence)
|
||||
// while still allowing environment variable overrides
|
||||
let worker_model_config = {
|
||||
// Start with a clone of the original model to preserve user-specified settings
|
||||
let mut worker_config = ModelConfig::new_or_fail(default_model.model_name.as_str())
|
||||
.with_context_limit(default_model.context_limit)
|
||||
.with_temperature(default_model.temperature)
|
||||
.with_max_tokens(default_model.max_tokens)
|
||||
.with_toolshim(default_model.toolshim)
|
||||
.with_toolshim_model(default_model.toolshim_model.clone());
|
||||
let worker_model_config = create_worker_model_config(default_model)?;
|
||||
|
||||
// Apply environment variable overrides with proper precedence
|
||||
let global_config = crate::config::Config::global();
|
||||
let lead_provider = REGISTRY
|
||||
.read()
|
||||
.unwrap()
|
||||
.create(&lead_provider_name, lead_model_config)?;
|
||||
let worker_provider = REGISTRY
|
||||
.read()
|
||||
.unwrap()
|
||||
.create(default_provider_name, worker_model_config)?;
|
||||
|
||||
// Check for worker-specific context limit
|
||||
if let Ok(limit_str) = global_config.get_param::<String>("GOOSE_WORKER_CONTEXT_LIMIT") {
|
||||
if let Ok(limit) = limit_str.parse::<usize>() {
|
||||
worker_config = worker_config.with_context_limit(Some(limit));
|
||||
}
|
||||
} else if let Ok(limit_str) = global_config.get_param::<String>("GOOSE_CONTEXT_LIMIT") {
|
||||
// Check for general context limit if worker-specific is not set
|
||||
if let Ok(limit) = limit_str.parse::<usize>() {
|
||||
worker_config = worker_config.with_context_limit(Some(limit));
|
||||
}
|
||||
}
|
||||
|
||||
worker_config
|
||||
};
|
||||
|
||||
// Create the providers
|
||||
let lead_provider = create_provider(&lead_provider_name, lead_model_config)?;
|
||||
let worker_provider = create_provider(default_provider_name, worker_model_config)?;
|
||||
|
||||
// Create the lead/worker provider with configured settings
|
||||
Ok(Arc::new(LeadWorkerProvider::new_with_settings(
|
||||
lead_provider,
|
||||
worker_provider,
|
||||
@@ -148,30 +144,27 @@ fn create_lead_worker_from_env(
|
||||
)))
|
||||
}
|
||||
|
||||
fn create_provider(name: &str, model: ModelConfig) -> Result<Arc<dyn Provider>> {
|
||||
// We use Arc instead of Box to be able to clone for multiple async tasks
|
||||
match name {
|
||||
"anthropic" => Ok(Arc::new(AnthropicProvider::from_env(model)?)),
|
||||
"aws_bedrock" => Ok(Arc::new(BedrockProvider::from_env(model)?)),
|
||||
"azure_openai" => Ok(Arc::new(AzureProvider::from_env(model)?)),
|
||||
"claude-code" => Ok(Arc::new(ClaudeCodeProvider::from_env(model)?)),
|
||||
"cursor-agent" => Ok(Arc::new(CursorAgentProvider::from_env(model)?)),
|
||||
"databricks" => Ok(Arc::new(DatabricksProvider::from_env(model)?)),
|
||||
"gcp_vertex_ai" => Ok(Arc::new(GcpVertexAIProvider::from_env(model)?)),
|
||||
"gemini-cli" => Ok(Arc::new(GeminiCliProvider::from_env(model)?)),
|
||||
// "github_copilot" => Ok(Arc::new(GithubCopilotProvider::from_env(model)?)),
|
||||
"google" => Ok(Arc::new(GoogleProvider::from_env(model)?)),
|
||||
"groq" => Ok(Arc::new(GroqProvider::from_env(model)?)),
|
||||
"litellm" => Ok(Arc::new(LiteLLMProvider::from_env(model)?)),
|
||||
"ollama" => Ok(Arc::new(OllamaProvider::from_env(model)?)),
|
||||
"openai" => Ok(Arc::new(OpenAiProvider::from_env(model)?)),
|
||||
"openrouter" => Ok(Arc::new(OpenRouterProvider::from_env(model)?)),
|
||||
"sagemaker_tgi" => Ok(Arc::new(SageMakerTgiProvider::from_env(model)?)),
|
||||
"snowflake" => Ok(Arc::new(SnowflakeProvider::from_env(model)?)),
|
||||
"venice" => Ok(Arc::new(VeniceProvider::from_env(model)?)),
|
||||
"xai" => Ok(Arc::new(XaiProvider::from_env(model)?)),
|
||||
_ => Err(anyhow::anyhow!("Unknown provider: {}", name)),
|
||||
fn create_worker_model_config(default_model: &ModelConfig) -> Result<ModelConfig> {
|
||||
let mut worker_config = ModelConfig::new_or_fail(&default_model.model_name)
|
||||
.with_context_limit(default_model.context_limit)
|
||||
.with_temperature(default_model.temperature)
|
||||
.with_max_tokens(default_model.max_tokens)
|
||||
.with_toolshim(default_model.toolshim)
|
||||
.with_toolshim_model(default_model.toolshim_model.clone());
|
||||
|
||||
let global_config = crate::config::Config::global();
|
||||
|
||||
if let Ok(limit_str) = global_config.get_param::<String>("GOOSE_WORKER_CONTEXT_LIMIT") {
|
||||
if let Ok(limit) = limit_str.parse::<usize>() {
|
||||
worker_config = worker_config.with_context_limit(Some(limit));
|
||||
}
|
||||
} else if let Ok(limit_str) = global_config.get_param::<String>("GOOSE_CONTEXT_LIMIT") {
|
||||
if let Ok(limit) = limit_str.parse::<usize>() {
|
||||
worker_config = worker_config.with_context_limit(Some(limit));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(worker_config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -183,7 +176,6 @@ mod tests {
|
||||
use rmcp::model::{AnnotateAble, RawTextContent, Role};
|
||||
use std::env;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
struct MockTestProvider {
|
||||
name: String,
|
||||
@@ -233,222 +225,141 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct EnvVarGuard {
|
||||
vars: Vec<(String, Option<String>)>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
fn new(vars: &[&str]) -> Self {
|
||||
let saved_vars = vars
|
||||
.iter()
|
||||
.map(|&var| (var.to_string(), env::var(var).ok()))
|
||||
.collect();
|
||||
|
||||
for &var in vars {
|
||||
env::remove_var(var);
|
||||
}
|
||||
|
||||
Self { vars: saved_vars }
|
||||
}
|
||||
|
||||
fn set(&self, key: &str, value: &str) {
|
||||
env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
for (key, value) in &self.vars {
|
||||
match value {
|
||||
Some(val) => env::set_var(key, val),
|
||||
None => env::remove_var(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_lead_worker_provider() {
|
||||
// Save current env vars
|
||||
let saved_lead = env::var("GOOSE_LEAD_MODEL").ok();
|
||||
let saved_provider = env::var("GOOSE_LEAD_PROVIDER").ok();
|
||||
let saved_turns = env::var("GOOSE_LEAD_TURNS").ok();
|
||||
let _guard = EnvVarGuard::new(&[
|
||||
"GOOSE_LEAD_MODEL",
|
||||
"GOOSE_LEAD_PROVIDER",
|
||||
"GOOSE_LEAD_TURNS",
|
||||
]);
|
||||
|
||||
// Test with basic lead model configuration
|
||||
env::set_var("GOOSE_LEAD_MODEL", "gpt-4o");
|
||||
_guard.set("GOOSE_LEAD_MODEL", "gpt-4o");
|
||||
|
||||
// This will try to create a lead/worker provider
|
||||
let gpt4mini_config = ModelConfig::new_or_fail("gpt-4o-mini");
|
||||
let result = create("openai", gpt4mini_config.clone());
|
||||
|
||||
// The creation might succeed or fail depending on API keys, but we can verify the logic path
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// If it succeeds, it means we created a lead/worker provider successfully
|
||||
// This would happen if API keys are available in the test environment
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
// If it fails, it should be due to missing API keys, confirming we tried to create providers
|
||||
let error_msg = error.to_string();
|
||||
assert!(error_msg.contains("OPENAI_API_KEY") || error_msg.contains("secret"));
|
||||
}
|
||||
}
|
||||
|
||||
// Test with different lead provider
|
||||
env::set_var("GOOSE_LEAD_PROVIDER", "anthropic");
|
||||
env::set_var("GOOSE_LEAD_TURNS", "5");
|
||||
_guard.set("GOOSE_LEAD_PROVIDER", "anthropic");
|
||||
_guard.set("GOOSE_LEAD_TURNS", "5");
|
||||
|
||||
let _result = create("openai", gpt4mini_config);
|
||||
// Similar validation as above - will fail due to missing API keys but confirms the logic
|
||||
|
||||
// Restore env vars
|
||||
match saved_lead {
|
||||
Some(val) => env::set_var("GOOSE_LEAD_MODEL", val),
|
||||
None => env::remove_var("GOOSE_LEAD_MODEL"),
|
||||
}
|
||||
match saved_provider {
|
||||
Some(val) => env::set_var("GOOSE_LEAD_PROVIDER", val),
|
||||
None => env::remove_var("GOOSE_LEAD_PROVIDER"),
|
||||
}
|
||||
match saved_turns {
|
||||
Some(val) => env::set_var("GOOSE_LEAD_TURNS", val),
|
||||
None => env::remove_var("GOOSE_LEAD_TURNS"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lead_model_env_vars_with_defaults() {
|
||||
// Save current env vars
|
||||
let saved_vars = [
|
||||
("GOOSE_LEAD_MODEL", env::var("GOOSE_LEAD_MODEL").ok()),
|
||||
("GOOSE_LEAD_PROVIDER", env::var("GOOSE_LEAD_PROVIDER").ok()),
|
||||
("GOOSE_LEAD_TURNS", env::var("GOOSE_LEAD_TURNS").ok()),
|
||||
(
|
||||
"GOOSE_LEAD_FAILURE_THRESHOLD",
|
||||
env::var("GOOSE_LEAD_FAILURE_THRESHOLD").ok(),
|
||||
),
|
||||
(
|
||||
"GOOSE_LEAD_FALLBACK_TURNS",
|
||||
env::var("GOOSE_LEAD_FALLBACK_TURNS").ok(),
|
||||
),
|
||||
];
|
||||
let _guard = EnvVarGuard::new(&[
|
||||
"GOOSE_LEAD_MODEL",
|
||||
"GOOSE_LEAD_PROVIDER",
|
||||
"GOOSE_LEAD_TURNS",
|
||||
"GOOSE_LEAD_FAILURE_THRESHOLD",
|
||||
"GOOSE_LEAD_FALLBACK_TURNS",
|
||||
]);
|
||||
|
||||
// Clear all lead env vars
|
||||
for (key, _) in &saved_vars {
|
||||
env::remove_var(key);
|
||||
}
|
||||
_guard.set("GOOSE_LEAD_MODEL", "grok-3");
|
||||
|
||||
// Set only the required lead model
|
||||
env::set_var("GOOSE_LEAD_MODEL", "grok-3");
|
||||
|
||||
// This should use defaults for all other values
|
||||
let result = create("openai", ModelConfig::new_or_fail("gpt-4o-mini"));
|
||||
|
||||
// Should attempt to create lead/worker provider (will fail due to missing API keys but confirms logic)
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// Success means we have API keys and created the provider
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
// Should fail due to missing API keys, confirming we tried to create providers
|
||||
let error_msg = error.to_string();
|
||||
assert!(error_msg.contains("OPENAI_API_KEY") || error_msg.contains("secret"));
|
||||
}
|
||||
}
|
||||
|
||||
// Test with custom values
|
||||
env::set_var("GOOSE_LEAD_TURNS", "7");
|
||||
env::set_var("GOOSE_LEAD_FAILURE_THRESHOLD", "4");
|
||||
env::set_var("GOOSE_LEAD_FALLBACK_TURNS", "3");
|
||||
_guard.set("GOOSE_LEAD_TURNS", "7");
|
||||
_guard.set("GOOSE_LEAD_FAILURE_THRESHOLD", "4");
|
||||
_guard.set("GOOSE_LEAD_FALLBACK_TURNS", "3");
|
||||
|
||||
let _result = create("openai", ModelConfig::new_or_fail("gpt-4o-mini"));
|
||||
// Should still attempt to create lead/worker provider with custom settings
|
||||
|
||||
// Restore all env vars
|
||||
for (key, value) in saved_vars {
|
||||
match value {
|
||||
Some(val) => env::set_var(key, val),
|
||||
None => env::remove_var(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_regular_provider_without_lead_config() {
|
||||
// Save current env vars
|
||||
let saved_lead = env::var("GOOSE_LEAD_MODEL").ok();
|
||||
let saved_provider = env::var("GOOSE_LEAD_PROVIDER").ok();
|
||||
let saved_turns = env::var("GOOSE_LEAD_TURNS").ok();
|
||||
let saved_threshold = env::var("GOOSE_LEAD_FAILURE_THRESHOLD").ok();
|
||||
let saved_fallback = env::var("GOOSE_LEAD_FALLBACK_TURNS").ok();
|
||||
let _guard = EnvVarGuard::new(&[
|
||||
"GOOSE_LEAD_MODEL",
|
||||
"GOOSE_LEAD_PROVIDER",
|
||||
"GOOSE_LEAD_TURNS",
|
||||
"GOOSE_LEAD_FAILURE_THRESHOLD",
|
||||
"GOOSE_LEAD_FALLBACK_TURNS",
|
||||
]);
|
||||
|
||||
// Ensure all GOOSE_LEAD_* variables are not set
|
||||
env::remove_var("GOOSE_LEAD_MODEL");
|
||||
env::remove_var("GOOSE_LEAD_PROVIDER");
|
||||
env::remove_var("GOOSE_LEAD_TURNS");
|
||||
env::remove_var("GOOSE_LEAD_FAILURE_THRESHOLD");
|
||||
env::remove_var("GOOSE_LEAD_FALLBACK_TURNS");
|
||||
|
||||
// This should try to create a regular provider
|
||||
let result = create("openai", ModelConfig::new_or_fail("gpt-4o-mini"));
|
||||
|
||||
// The creation might succeed or fail depending on API keys
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// If it succeeds, it means we created a regular provider successfully
|
||||
// This would happen if API keys are available in the test environment
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
// If it fails, it should be due to missing API keys
|
||||
let error_msg = error.to_string();
|
||||
assert!(error_msg.contains("OPENAI_API_KEY") || error_msg.contains("secret"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = saved_lead {
|
||||
env::set_var("GOOSE_LEAD_MODEL", val);
|
||||
}
|
||||
if let Some(val) = saved_provider {
|
||||
env::set_var("GOOSE_LEAD_PROVIDER", val);
|
||||
}
|
||||
if let Some(val) = saved_turns {
|
||||
env::set_var("GOOSE_LEAD_TURNS", val);
|
||||
}
|
||||
if let Some(val) = saved_threshold {
|
||||
env::set_var("GOOSE_LEAD_FAILURE_THRESHOLD", val);
|
||||
}
|
||||
if let Some(val) = saved_fallback {
|
||||
env::set_var("GOOSE_LEAD_FALLBACK_TURNS", val);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_worker_model_preserves_original_context_limit() {
|
||||
use std::env;
|
||||
let _guard = EnvVarGuard::new(&[
|
||||
"GOOSE_LEAD_MODEL",
|
||||
"GOOSE_WORKER_CONTEXT_LIMIT",
|
||||
"GOOSE_CONTEXT_LIMIT",
|
||||
]);
|
||||
|
||||
// Save current env vars
|
||||
let saved_vars = [
|
||||
("GOOSE_LEAD_MODEL", env::var("GOOSE_LEAD_MODEL").ok()),
|
||||
(
|
||||
"GOOSE_WORKER_CONTEXT_LIMIT",
|
||||
env::var("GOOSE_WORKER_CONTEXT_LIMIT").ok(),
|
||||
),
|
||||
("GOOSE_CONTEXT_LIMIT", env::var("GOOSE_CONTEXT_LIMIT").ok()),
|
||||
];
|
||||
_guard.set("GOOSE_LEAD_MODEL", "gpt-4o");
|
||||
|
||||
// Clear env vars to ensure clean test
|
||||
for (key, _) in &saved_vars {
|
||||
env::remove_var(key);
|
||||
}
|
||||
|
||||
// Set up lead model to trigger lead/worker mode
|
||||
env::set_var("GOOSE_LEAD_MODEL", "gpt-4o");
|
||||
|
||||
// Create a default model with explicit context_limit
|
||||
let default_model =
|
||||
ModelConfig::new_or_fail("gpt-3.5-turbo").with_context_limit(Some(16_000));
|
||||
|
||||
// Test case 1: No environment variables - should preserve original context_limit
|
||||
let result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
|
||||
|
||||
// Test case 2: With GOOSE_WORKER_CONTEXT_LIMIT - should override original
|
||||
env::set_var("GOOSE_WORKER_CONTEXT_LIMIT", "32000");
|
||||
_guard.set("GOOSE_WORKER_CONTEXT_LIMIT", "32000");
|
||||
let _result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
|
||||
env::remove_var("GOOSE_WORKER_CONTEXT_LIMIT");
|
||||
|
||||
// Test case 3: With GOOSE_CONTEXT_LIMIT - should override original
|
||||
env::set_var("GOOSE_CONTEXT_LIMIT", "64000");
|
||||
_guard.set("GOOSE_CONTEXT_LIMIT", "64000");
|
||||
let _result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
|
||||
env::remove_var("GOOSE_CONTEXT_LIMIT");
|
||||
|
||||
// Restore env vars
|
||||
for (key, value) in saved_vars {
|
||||
match value {
|
||||
Some(val) => env::set_var(key, val),
|
||||
None => env::remove_var(key),
|
||||
}
|
||||
}
|
||||
|
||||
// The main verification is that the function doesn't panic and handles
|
||||
// the context limit preservation logic correctly. More detailed testing
|
||||
// would require mocking the provider creation.
|
||||
// The result could be Ok or Err depending on whether API keys are available
|
||||
// in the test environment - both are acceptable for this test
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// Success means API keys are available and lead/worker provider was created
|
||||
// This confirms our logic path is working
|
||||
}
|
||||
Err(_) => {
|
||||
// Error is expected if API keys are not available
|
||||
// This also confirms our logic path is working
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub mod ollama;
|
||||
pub mod openai;
|
||||
pub mod openrouter;
|
||||
pub mod pricing;
|
||||
pub mod provider_registry;
|
||||
mod retry;
|
||||
pub mod sagemaker_tgi;
|
||||
pub mod snowflake;
|
||||
@@ -35,4 +36,4 @@ pub mod utils_universal_openai_stream;
|
||||
pub mod venice;
|
||||
pub mod xai;
|
||||
|
||||
pub use factory::{create, providers};
|
||||
pub use factory::{create, providers, refresh_custom_providers};
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage};
|
||||
use super::errors::ProviderError;
|
||||
use super::retry::ProviderRetry;
|
||||
use super::utils::{get_model, handle_response_openai_compat};
|
||||
use crate::config::custom_providers::CustomProviderConfig;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::conversation::Conversation;
|
||||
use crate::impl_provider_default;
|
||||
@@ -30,6 +31,7 @@ pub struct OllamaProvider {
|
||||
#[serde(skip)]
|
||||
api_client: ApiClient,
|
||||
model: ModelConfig,
|
||||
supports_streaming: bool,
|
||||
}
|
||||
|
||||
impl_provider_default!(OllamaProvider);
|
||||
@@ -73,7 +75,47 @@ impl OllamaProvider {
|
||||
let auth = AuthMethod::Custom(Box::new(NoAuth));
|
||||
let api_client = ApiClient::with_timeout(base_url.to_string(), auth, timeout)?;
|
||||
|
||||
Ok(Self { api_client, model })
|
||||
Ok(Self {
|
||||
api_client,
|
||||
model,
|
||||
supports_streaming: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_custom_config(model: ModelConfig, config: CustomProviderConfig) -> Result<Self> {
|
||||
let timeout = Duration::from_secs(config.timeout_seconds.unwrap_or(OLLAMA_TIMEOUT));
|
||||
|
||||
// Parse and normalize the custom URL
|
||||
let base =
|
||||
if config.base_url.starts_with("http://") || config.base_url.starts_with("https://") {
|
||||
config.base_url.clone()
|
||||
} else {
|
||||
format!("http://{}", config.base_url)
|
||||
};
|
||||
|
||||
let mut base_url = Url::parse(&base)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid base URL '{}': {}", config.base_url, e))?;
|
||||
|
||||
// Set default port if missing and not using standard ports
|
||||
let explicit_default_port =
|
||||
config.base_url.ends_with(":80") || config.base_url.ends_with(":443");
|
||||
let is_https = base_url.scheme() == "https";
|
||||
|
||||
if base_url.port().is_none() && !explicit_default_port && !is_https {
|
||||
base_url
|
||||
.set_port(Some(OLLAMA_DEFAULT_PORT))
|
||||
.map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
|
||||
}
|
||||
|
||||
// No authentication for Ollama
|
||||
let auth = AuthMethod::Custom(Box::new(NoAuth));
|
||||
let api_client = ApiClient::with_timeout(base_url.to_string(), auth, timeout)?;
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
model,
|
||||
supports_streaming: config.supports_streaming.unwrap_or(true),
|
||||
})
|
||||
}
|
||||
|
||||
async fn post(&self, payload: &Value) -> Result<Value, ProviderError> {
|
||||
@@ -181,6 +223,10 @@ impl Provider for OllamaProvider {
|
||||
|
||||
Ok(safe_truncate(&description, 100))
|
||||
}
|
||||
|
||||
fn supports_streaming(&self) -> bool {
|
||||
self.supports_streaming
|
||||
}
|
||||
}
|
||||
|
||||
impl OllamaProvider {
|
||||
|
||||
@@ -20,6 +20,7 @@ use super::utils::{
|
||||
emit_debug_trace, get_model, handle_response_openai_compat, handle_status_openai_compat,
|
||||
ImageFormat,
|
||||
};
|
||||
use crate::config::custom_providers::CustomProviderConfig;
|
||||
use crate::conversation::message::Message;
|
||||
use crate::impl_provider_default;
|
||||
use crate::model::ModelConfig;
|
||||
@@ -51,6 +52,7 @@ pub struct OpenAiProvider {
|
||||
project: Option<String>,
|
||||
model: ModelConfig,
|
||||
custom_headers: Option<HashMap<String, String>>,
|
||||
supports_streaming: bool,
|
||||
}
|
||||
|
||||
impl_provider_default!(OpenAiProvider);
|
||||
@@ -103,6 +105,51 @@ impl OpenAiProvider {
|
||||
project,
|
||||
model,
|
||||
custom_headers,
|
||||
supports_streaming: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_custom_config(model: ModelConfig, config: CustomProviderConfig) -> Result<Self> {
|
||||
let global_config = crate::config::Config::global();
|
||||
let api_key: String = global_config
|
||||
.get_secret(&config.api_key_env)
|
||||
.map_err(|_e| anyhow::anyhow!("Missing API key: {}", config.api_key_env))?;
|
||||
|
||||
let url = url::Url::parse(&config.base_url)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid base URL '{}': {}", config.base_url, e))?;
|
||||
|
||||
let host = format!("{}://{}", url.scheme(), url.host_str().unwrap_or(""));
|
||||
let base_path = url.path().trim_start_matches('/').to_string();
|
||||
let base_path = if base_path.is_empty() {
|
||||
"v1/chat/completions".to_string()
|
||||
} else {
|
||||
base_path
|
||||
};
|
||||
|
||||
let timeout_secs = config.timeout_seconds.unwrap_or(600);
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
let mut api_client =
|
||||
ApiClient::with_timeout(host, auth, std::time::Duration::from_secs(timeout_secs))?;
|
||||
|
||||
// Add custom headers if present
|
||||
if let Some(headers) = &config.headers {
|
||||
let mut header_map = reqwest::header::HeaderMap::new();
|
||||
for (key, value) in headers {
|
||||
let header_name = reqwest::header::HeaderName::from_bytes(key.as_bytes())?;
|
||||
let header_value = reqwest::header::HeaderValue::from_str(value)?;
|
||||
header_map.insert(header_name, header_value);
|
||||
}
|
||||
api_client = api_client.with_headers(header_map)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
base_path,
|
||||
organization: None,
|
||||
project: None,
|
||||
model,
|
||||
custom_headers: config.headers,
|
||||
supports_streaming: config.supports_streaming.unwrap_or(true),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -206,7 +253,7 @@ impl Provider for OpenAiProvider {
|
||||
}
|
||||
|
||||
fn supports_streaming(&self) -> bool {
|
||||
true
|
||||
self.supports_streaming
|
||||
}
|
||||
|
||||
async fn stream(
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
use super::base::{Provider, ProviderMetadata};
|
||||
use crate::model::ModelConfig;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
type ProviderConstructor = Box<dyn Fn(ModelConfig) -> Result<Arc<dyn Provider>> + Send + Sync>;
|
||||
|
||||
struct ProviderEntry {
|
||||
metadata: ProviderMetadata,
|
||||
constructor: ProviderConstructor,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProviderRegistry {
|
||||
entries: HashMap<String, ProviderEntry>,
|
||||
}
|
||||
|
||||
impl ProviderRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<P, F>(&mut self, constructor: F)
|
||||
where
|
||||
P: Provider + 'static,
|
||||
F: Fn(ModelConfig) -> Result<P> + Send + Sync + 'static,
|
||||
{
|
||||
let metadata = P::metadata();
|
||||
let name = metadata.name.clone();
|
||||
|
||||
self.entries.insert(
|
||||
name,
|
||||
ProviderEntry {
|
||||
metadata,
|
||||
constructor: Box::new(move |model| Ok(Arc::new(constructor(model)?))),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// create provider with custom name
|
||||
pub fn register_with_name<P, F>(
|
||||
&mut self,
|
||||
custom_name: String,
|
||||
display_name: String,
|
||||
description: String,
|
||||
default_model: String,
|
||||
known_models: Vec<super::base::ModelInfo>,
|
||||
constructor: F,
|
||||
) where
|
||||
P: Provider + 'static,
|
||||
F: Fn(ModelConfig) -> Result<P> + Send + Sync + 'static,
|
||||
{
|
||||
let base_metadata = P::metadata();
|
||||
let custom_metadata = ProviderMetadata {
|
||||
name: custom_name.clone(),
|
||||
display_name,
|
||||
description,
|
||||
default_model,
|
||||
known_models,
|
||||
model_doc_link: base_metadata.model_doc_link,
|
||||
config_keys: base_metadata.config_keys,
|
||||
};
|
||||
|
||||
self.entries.insert(
|
||||
custom_name,
|
||||
ProviderEntry {
|
||||
metadata: custom_metadata,
|
||||
constructor: Box::new(move |model| Ok(Arc::new(constructor(model)?))),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn with_providers<F>(mut self, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
setup(&mut self);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn create(&self, name: &str, model: ModelConfig) -> Result<Arc<dyn Provider>> {
|
||||
let _available_providers: Vec<_> = self.entries.keys().collect();
|
||||
|
||||
let entry = self
|
||||
.entries
|
||||
.get(name)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unknown provider: {}", name))?;
|
||||
|
||||
(entry.constructor)(model)
|
||||
}
|
||||
|
||||
pub fn all_metadata(&self) -> Vec<ProviderMetadata> {
|
||||
self.entries.values().map(|e| e.metadata.clone()).collect()
|
||||
}
|
||||
|
||||
pub fn remove_custom_providers(&mut self) {
|
||||
self.entries.retain(|name, _| !name.starts_with("custom_"));
|
||||
}
|
||||
}
|
||||
@@ -275,6 +275,78 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/custom-providers": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "create_custom_provider",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateCustomProviderRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom provider created successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/custom-providers/{id}": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "remove_custom_provider",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom provider removed successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Provider not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/extensions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1469,6 +1541,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateCustomProviderRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider_type",
|
||||
"display_name",
|
||||
"api_url",
|
||||
"api_key",
|
||||
"models"
|
||||
],
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"api_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"provider_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"supports_streaming": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateRecipeRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Generated
+217
-216
@@ -119,13 +119,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ai-sdk/gateway": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.6.tgz",
|
||||
"integrity": "sha512-JuSj1MtTr4vw2VBBth4wlbciQnQIV0o1YV9qGLFA+r85nR5H+cJp3jaYE0nprqfzC9rYG8w9c6XGHB3SDKgcgA==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.8.tgz",
|
||||
"integrity": "sha512-yiHYz0bAHEvhL+fSUBI2dNmyj0LOI8zw5qrYpa4gp1ojPgZq/7T1WXoIWRmVdjQwvT4PzSmRKLtbMPfz+umgfw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@ai-sdk/provider-utils": "3.0.3"
|
||||
"@ai-sdk/provider-utils": "3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -135,13 +135,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/openai": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.14.tgz",
|
||||
"integrity": "sha512-u/wi1ixcvcg29wAJySjO803HlXpyCl6mkcOHn+Zn7DA+CtjuQNkKikJ4pZBc7I3Qhi90kA4XnOfKikhBXh4c4Q==",
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.16.tgz",
|
||||
"integrity": "sha512-Boe715q4SkSJedFfAtbP0yuo8DmF9iYElAaDH2g4YgqJqqkskIJJx4hlCYGMMk1eMesRrB2NqZvtOeyTZ/u4fA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@ai-sdk/provider-utils": "3.0.3"
|
||||
"@ai-sdk/provider-utils": "3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -163,9 +163,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.3.tgz",
|
||||
"integrity": "sha512-kAxIw1nYmFW1g5TvE54ZB3eNtgZna0RnLjPUp1ltz1+t9xkXJIuDT4atrwfau9IbS0BOef38wqrI8CjFfQrxhw==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.4.tgz",
|
||||
"integrity": "sha512-/3Z6lfUp8r+ewFd9yzHkCmPlMOJUXup2Sx3aoUyrdXLhOmAfHRl6Z4lDbIdV0uvw/QYoBcVLJnvXN7ncYeS3uQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
@@ -2367,9 +2367,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hey-api/openapi-ts": {
|
||||
"version": "0.80.10",
|
||||
"resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.80.10.tgz",
|
||||
"integrity": "sha512-jFCaQJeZV9B6lbYCU8JFVkC2/FE+31FG36aT4aiOXeGvgJsIa7v3HFXQ3sdcN0uHRuLyf11GUDrVupPBPvgkuA==",
|
||||
"version": "0.80.15",
|
||||
"resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.80.15.tgz",
|
||||
"integrity": "sha512-THERBZUJG9lKHBqY7yBWL8icuwLJc/BY0kjuuZKZST/ZZzt+Zl78nMjP1RFaKybyT8yX0YlA/liem51dSSf+9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2379,6 +2379,7 @@
|
||||
"color-support": "1.1.3",
|
||||
"commander": "13.0.0",
|
||||
"handlebars": "4.7.8",
|
||||
"js-yaml": "4.1.0",
|
||||
"open": "10.1.2",
|
||||
"semver": "7.7.2"
|
||||
},
|
||||
@@ -3108,9 +3109,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@preact/signals-core": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.11.0.tgz",
|
||||
"integrity": "sha512-jglbibeWHuFRzEWVFY/TT7wB1PppJxmcSfUHcK+2J9vBRtiooMfw6tAPttojNYrrpdGViqAYCbPpmWYlMm+eMQ==",
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.12.0.tgz",
|
||||
"integrity": "sha512-etWpENXm469RHMWIZGblgWrapbIGcRcbccEGGaLkFez3PjlI3XkBrUtSiNFsIfV/DN16PxMOxbWAZUIaLFyJDg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -4741,16 +4742,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw==",
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz",
|
||||
"integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
|
||||
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.3.tgz",
|
||||
"integrity": "sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4762,9 +4763,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.3.tgz",
|
||||
"integrity": "sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4776,9 +4777,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.3.tgz",
|
||||
"integrity": "sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4790,9 +4791,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
|
||||
"integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.3.tgz",
|
||||
"integrity": "sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4804,9 +4805,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
|
||||
"integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.3.tgz",
|
||||
"integrity": "sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4818,9 +4819,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
|
||||
"integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.3.tgz",
|
||||
"integrity": "sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4832,9 +4833,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
|
||||
"integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.3.tgz",
|
||||
"integrity": "sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4846,9 +4847,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
|
||||
"integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.3.tgz",
|
||||
"integrity": "sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4860,9 +4861,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4874,9 +4875,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.3.tgz",
|
||||
"integrity": "sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4888,9 +4889,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -4902,9 +4903,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -4916,9 +4917,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -4930,9 +4931,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.3.tgz",
|
||||
"integrity": "sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -4944,9 +4945,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -4958,9 +4959,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
|
||||
"integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz",
|
||||
"integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4972,9 +4973,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
|
||||
"integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.3.tgz",
|
||||
"integrity": "sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4986,9 +4987,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.3.tgz",
|
||||
"integrity": "sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -5000,9 +5001,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.3.tgz",
|
||||
"integrity": "sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -5014,9 +5015,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
|
||||
"integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.3.tgz",
|
||||
"integrity": "sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -5807,9 +5808,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
|
||||
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
|
||||
"version": "24.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
||||
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5951,17 +5952,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
|
||||
"integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz",
|
||||
"integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/type-utils": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/type-utils": "8.40.0",
|
||||
"@typescript-eslint/utils": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -5975,22 +5976,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.39.1",
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
|
||||
"integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz",
|
||||
"integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6006,14 +6007,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
|
||||
"integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz",
|
||||
"integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.1",
|
||||
"@typescript-eslint/types": "^8.39.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.40.0",
|
||||
"@typescript-eslint/types": "^8.40.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6028,14 +6029,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
|
||||
"integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz",
|
||||
"integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1"
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -6046,9 +6047,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz",
|
||||
"integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6063,15 +6064,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz",
|
||||
"integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0",
|
||||
"@typescript-eslint/utils": "8.40.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -6088,9 +6089,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
|
||||
"integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz",
|
||||
"integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6102,16 +6103,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
|
||||
"integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz",
|
||||
"integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.39.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"@typescript-eslint/project-service": "8.40.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/visitor-keys": "8.40.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -6157,16 +6158,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
|
||||
"integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz",
|
||||
"integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1"
|
||||
"@typescript-eslint/scope-manager": "8.40.0",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"@typescript-eslint/typescript-estree": "8.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -6181,13 +6182,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
|
||||
"integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
|
||||
"version": "8.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz",
|
||||
"integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/types": "8.40.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6218,16 +6219,16 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.0.tgz",
|
||||
"integrity": "sha512-Jx9JfsTa05bYkS9xo0hkofp2dCmp1blrKjw9JONs5BTHOvJCgLbaPSuZLGSVJW6u2qe0tc4eevY0+gSNNi0YCw==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz",
|
||||
"integrity": "sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.28.0",
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.30",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.32",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"react-refresh": "^0.17.0"
|
||||
},
|
||||
@@ -6410,9 +6411,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6500,14 +6501,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ai": {
|
||||
"version": "5.0.14",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.14.tgz",
|
||||
"integrity": "sha512-xiujFa879skB7YxGzbeHAxepsr6AEaWcHPXrc5a9MRM6p4WdVAwn6mGwVZkBnhqGfZtXFr4LUnU2ayvcjWp5ig==",
|
||||
"version": "5.0.17",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.17.tgz",
|
||||
"integrity": "sha512-DLZikqZZJdwSkRhFikw6Mt7pUmPZ7Ue38TjdOcw2U6iZtBbuiyWGIhHyJXlUpLcZrtBE5yqPTozyZri1lRjduw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/gateway": "1.0.6",
|
||||
"@ai-sdk/gateway": "1.0.8",
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@ai-sdk/provider-utils": "3.0.3",
|
||||
"@ai-sdk/provider-utils": "3.0.4",
|
||||
"@opentelemetry/api": "1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6990,9 +6991,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.25.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz",
|
||||
"integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==",
|
||||
"version": "4.25.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz",
|
||||
"integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -7010,8 +7011,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001733",
|
||||
"electron-to-chromium": "^1.5.199",
|
||||
"caniuse-lite": "^1.0.30001735",
|
||||
"electron-to-chromium": "^1.5.204",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.3"
|
||||
},
|
||||
@@ -7376,9 +7377,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz",
|
||||
"integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==",
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.1.tgz",
|
||||
"integrity": "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8869,9 +8870,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-log": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.2.tgz",
|
||||
"integrity": "sha512-L55kJzbVVoBY6kyJ3A+cjPchL1aXLdV2/Q8SvIj4sE5VmOcwZa6KXaFICZE+Z+RMXhAIFta79kSaXBqn+0XXMA==",
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz",
|
||||
"integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -8902,9 +8903,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.201",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz",
|
||||
"integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==",
|
||||
"version": "1.5.207",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz",
|
||||
"integrity": "sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -9009,9 +9010,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "22.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz",
|
||||
"integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==",
|
||||
"version": "22.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz",
|
||||
"integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9736,9 +9737,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
|
||||
"integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.5.tgz",
|
||||
"integrity": "sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -11962,9 +11963,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
|
||||
"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -12583,9 +12584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged/node_modules/chalk": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz",
|
||||
"integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==",
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz",
|
||||
"integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -15916,9 +15917,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz",
|
||||
"integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz",
|
||||
"integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
@@ -15938,12 +15939,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz",
|
||||
"integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.1.tgz",
|
||||
"integrity": "sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.8.0"
|
||||
"react-router": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -16610,9 +16611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.46.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
|
||||
"integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
|
||||
"version": "4.46.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz",
|
||||
"integrity": "sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -16626,26 +16627,26 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.2",
|
||||
"@rollup/rollup-android-arm64": "4.46.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.2",
|
||||
"@rollup/rollup-darwin-x64": "4.46.2",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.2",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.2",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.2",
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.3",
|
||||
"@rollup/rollup-android-arm64": "4.46.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.3",
|
||||
"@rollup/rollup-darwin-x64": "4.46.3",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.3",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.3",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.3",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -17557,9 +17558,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
|
||||
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -18171,9 +18172,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tw-animate-css": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.6.tgz",
|
||||
"integrity": "sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==",
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.7.tgz",
|
||||
"integrity": "sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Wombosvideo"
|
||||
@@ -18678,14 +18679,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
|
||||
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
|
||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.43.0",
|
||||
|
||||
@@ -188,6 +188,25 @@ export const getParseAs = (
|
||||
return;
|
||||
};
|
||||
|
||||
const checkForExistence = (
|
||||
options: Pick<RequestOptions, 'auth' | 'query'> & {
|
||||
headers: Headers;
|
||||
},
|
||||
name?: string,
|
||||
): boolean => {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
options.headers.has(name) ||
|
||||
options.query?.[name] ||
|
||||
options.headers.get('Cookie')?.includes(`${name}=`)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const setAuthParams = async ({
|
||||
security,
|
||||
...options
|
||||
@@ -196,6 +215,10 @@ export const setAuthParams = async ({
|
||||
headers: Headers;
|
||||
}) => {
|
||||
for (const auth of security) {
|
||||
if (checkForExistence(options, auth.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const token = await getAuthToken(auth, options.auth);
|
||||
|
||||
if (!token) {
|
||||
@@ -219,8 +242,6 @@ export const setAuthParams = async ({
|
||||
options.headers.set(name, token);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Options as ClientOptions, TDataShape, Client } from './client';
|
||||
import type { AddSubRecipesData, AddSubRecipesResponses, AddSubRecipesErrors, ExtendPromptData, ExtendPromptResponses, ExtendPromptErrors, UpdateSessionConfigData, UpdateSessionConfigResponses, UpdateSessionConfigErrors, GetToolsData, GetToolsResponses, GetToolsErrors, UpdateAgentProviderData, UpdateAgentProviderResponses, UpdateAgentProviderErrors, UpdateRouterToolSelectorData, UpdateRouterToolSelectorResponses, UpdateRouterToolSelectorErrors, ReadAllConfigData, ReadAllConfigResponses, BackupConfigData, BackupConfigResponses, BackupConfigErrors, GetExtensionsData, GetExtensionsResponses, GetExtensionsErrors, AddExtensionData, AddExtensionResponses, AddExtensionErrors, RemoveExtensionData, RemoveExtensionResponses, RemoveExtensionErrors, InitConfigData, InitConfigResponses, InitConfigErrors, UpsertPermissionsData, UpsertPermissionsResponses, UpsertPermissionsErrors, ProvidersData, ProvidersResponses, ReadConfigData, ReadConfigResponses, ReadConfigErrors, RecoverConfigData, RecoverConfigResponses, RecoverConfigErrors, RemoveConfigData, RemoveConfigResponses, RemoveConfigErrors, UpsertConfigData, UpsertConfigResponses, UpsertConfigErrors, ValidateConfigData, ValidateConfigResponses, ValidateConfigErrors, ConfirmPermissionData, ConfirmPermissionResponses, ConfirmPermissionErrors, ManageContextData, ManageContextResponses, ManageContextErrors, CreateRecipeData, CreateRecipeResponses, CreateRecipeErrors, DecodeRecipeData, DecodeRecipeResponses, DecodeRecipeErrors, EncodeRecipeData, EncodeRecipeResponses, EncodeRecipeErrors, ScanRecipeData, ScanRecipeResponses, CreateScheduleData, CreateScheduleResponses, CreateScheduleErrors, DeleteScheduleData, DeleteScheduleResponses, DeleteScheduleErrors, ListSchedulesData, ListSchedulesResponses, ListSchedulesErrors, UpdateScheduleData, UpdateScheduleResponses, UpdateScheduleErrors, InspectRunningJobData, InspectRunningJobResponses, InspectRunningJobErrors, KillRunningJobData, KillRunningJobResponses, PauseScheduleData, PauseScheduleResponses, PauseScheduleErrors, RunNowHandlerData, RunNowHandlerResponses, RunNowHandlerErrors, SessionsHandlerData, SessionsHandlerResponses, SessionsHandlerErrors, UnpauseScheduleData, UnpauseScheduleResponses, UnpauseScheduleErrors, ListSessionsData, ListSessionsResponses, ListSessionsErrors, GetSessionHistoryData, GetSessionHistoryResponses, GetSessionHistoryErrors } from './types.gen';
|
||||
import type { AddSubRecipesData, AddSubRecipesResponses, AddSubRecipesErrors, ExtendPromptData, ExtendPromptResponses, ExtendPromptErrors, UpdateSessionConfigData, UpdateSessionConfigResponses, UpdateSessionConfigErrors, GetToolsData, GetToolsResponses, GetToolsErrors, UpdateAgentProviderData, UpdateAgentProviderResponses, UpdateAgentProviderErrors, UpdateRouterToolSelectorData, UpdateRouterToolSelectorResponses, UpdateRouterToolSelectorErrors, ReadAllConfigData, ReadAllConfigResponses, BackupConfigData, BackupConfigResponses, BackupConfigErrors, CreateCustomProviderData, CreateCustomProviderResponses, CreateCustomProviderErrors, RemoveCustomProviderData, RemoveCustomProviderResponses, RemoveCustomProviderErrors, GetExtensionsData, GetExtensionsResponses, GetExtensionsErrors, AddExtensionData, AddExtensionResponses, AddExtensionErrors, RemoveExtensionData, RemoveExtensionResponses, RemoveExtensionErrors, InitConfigData, InitConfigResponses, InitConfigErrors, UpsertPermissionsData, UpsertPermissionsResponses, UpsertPermissionsErrors, ProvidersData, ProvidersResponses, ReadConfigData, ReadConfigResponses, ReadConfigErrors, RecoverConfigData, RecoverConfigResponses, RecoverConfigErrors, RemoveConfigData, RemoveConfigResponses, RemoveConfigErrors, UpsertConfigData, UpsertConfigResponses, UpsertConfigErrors, ValidateConfigData, ValidateConfigResponses, ValidateConfigErrors, ConfirmPermissionData, ConfirmPermissionResponses, ConfirmPermissionErrors, ManageContextData, ManageContextResponses, ManageContextErrors, CreateRecipeData, CreateRecipeResponses, CreateRecipeErrors, DecodeRecipeData, DecodeRecipeResponses, DecodeRecipeErrors, EncodeRecipeData, EncodeRecipeResponses, EncodeRecipeErrors, ScanRecipeData, ScanRecipeResponses, CreateScheduleData, CreateScheduleResponses, CreateScheduleErrors, DeleteScheduleData, DeleteScheduleResponses, DeleteScheduleErrors, ListSchedulesData, ListSchedulesResponses, ListSchedulesErrors, UpdateScheduleData, UpdateScheduleResponses, UpdateScheduleErrors, InspectRunningJobData, InspectRunningJobResponses, InspectRunningJobErrors, KillRunningJobData, KillRunningJobResponses, PauseScheduleData, PauseScheduleResponses, PauseScheduleErrors, RunNowHandlerData, RunNowHandlerResponses, RunNowHandlerErrors, SessionsHandlerData, SessionsHandlerResponses, SessionsHandlerErrors, UnpauseScheduleData, UnpauseScheduleResponses, UnpauseScheduleErrors, ListSessionsData, ListSessionsResponses, ListSessionsErrors, GetSessionHistoryData, GetSessionHistoryResponses, GetSessionHistoryErrors } from './types.gen';
|
||||
import { client as _heyApiClient } from './client.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||
@@ -90,6 +90,24 @@ export const backupConfig = <ThrowOnError extends boolean = false>(options?: Opt
|
||||
});
|
||||
};
|
||||
|
||||
export const createCustomProvider = <ThrowOnError extends boolean = false>(options: Options<CreateCustomProviderData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).post<CreateCustomProviderResponses, CreateCustomProviderErrors, ThrowOnError>({
|
||||
url: '/config/custom-providers',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const removeCustomProvider = <ThrowOnError extends boolean = false>(options: Options<RemoveCustomProviderData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).delete<RemoveCustomProviderResponses, RemoveCustomProviderErrors, ThrowOnError>({
|
||||
url: '/config/custom-providers/{id}',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const getExtensions = <ThrowOnError extends boolean = false>(options?: Options<GetExtensionsData, ThrowOnError>) => {
|
||||
return (options?.client ?? _heyApiClient).get<GetExtensionsResponses, GetExtensionsErrors, ThrowOnError>({
|
||||
url: '/config/extensions',
|
||||
|
||||
@@ -98,6 +98,15 @@ export type ContextManageResponse = {
|
||||
tokenCounts: Array<number>;
|
||||
};
|
||||
|
||||
export type CreateCustomProviderRequest = {
|
||||
api_key: string;
|
||||
api_url: string;
|
||||
display_name: string;
|
||||
models: Array<string>;
|
||||
provider_type: string;
|
||||
supports_streaming?: boolean | null;
|
||||
};
|
||||
|
||||
export type CreateRecipeRequest = {
|
||||
activities?: Array<string> | null;
|
||||
author?: AuthorRequest | null;
|
||||
@@ -1047,6 +1056,62 @@ export type BackupConfigResponses = {
|
||||
|
||||
export type BackupConfigResponse = BackupConfigResponses[keyof BackupConfigResponses];
|
||||
|
||||
export type CreateCustomProviderData = {
|
||||
body: CreateCustomProviderRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/custom-providers';
|
||||
};
|
||||
|
||||
export type CreateCustomProviderErrors = {
|
||||
/**
|
||||
* Invalid request
|
||||
*/
|
||||
400: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type CreateCustomProviderResponses = {
|
||||
/**
|
||||
* Custom provider created successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type CreateCustomProviderResponse = CreateCustomProviderResponses[keyof CreateCustomProviderResponses];
|
||||
|
||||
export type RemoveCustomProviderData = {
|
||||
body?: never;
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/custom-providers/{id}';
|
||||
};
|
||||
|
||||
export type RemoveCustomProviderErrors = {
|
||||
/**
|
||||
* Provider not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type RemoveCustomProviderResponses = {
|
||||
/**
|
||||
* Custom provider removed successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type RemoveCustomProviderResponse = RemoveCustomProviderResponses[keyof RemoveCustomProviderResponses];
|
||||
|
||||
export type GetExtensionsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -170,9 +170,15 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
const getProviders = useCallback(
|
||||
async (forceRefresh = false): Promise<ProviderDetails[]> => {
|
||||
if (forceRefresh || providersList.length === 0) {
|
||||
const response = await providers();
|
||||
setProvidersList(response.data || []);
|
||||
return response.data || [];
|
||||
try {
|
||||
const response = await providers();
|
||||
const providersData = response.data || [];
|
||||
setProvidersList(providersData);
|
||||
return providersData;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch providers:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return providersList;
|
||||
},
|
||||
@@ -189,9 +195,11 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
// Load providers
|
||||
try {
|
||||
const providersResponse = await providers();
|
||||
setProvidersList(providersResponse.data || []);
|
||||
const providersData = providersResponse.data || [];
|
||||
setProvidersList(providersData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load providers:', error);
|
||||
setProvidersList([]);
|
||||
}
|
||||
|
||||
// Load extensions
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { screen, waitFor } from '@testing-library/dom';
|
||||
import MarkdownContent from './MarkdownContent';
|
||||
|
||||
// Mock the icons to avoid import issues
|
||||
|
||||
@@ -159,11 +159,14 @@ export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
|
||||
|
||||
// Add the "Custom model" option to each provider group
|
||||
formattedModelOptions.forEach((group) => {
|
||||
group.options.push({
|
||||
value: 'custom',
|
||||
label: 'Use custom model',
|
||||
provider: group.options[0]?.provider,
|
||||
});
|
||||
const providerName = group.options[0]?.provider;
|
||||
if (providerName && !providerName.startsWith('custom_')) {
|
||||
group.options.push({
|
||||
value: 'custom',
|
||||
label: 'Use custom model',
|
||||
provider: providerName,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setModelOptions(formattedModelOptions);
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
|
||||
console.error('Failed to get tools');
|
||||
} else {
|
||||
const filteredTools = (response.data || []).filter(
|
||||
(tool) =>
|
||||
(tool: ToolInfo) =>
|
||||
tool.name !== 'platform__read_resource' && tool.name !== 'platform__list_resources'
|
||||
);
|
||||
setTools(filteredTools);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React, { memo, useMemo, useCallback } from 'react';
|
||||
import React, { memo, useMemo, useCallback, useState } from 'react';
|
||||
import { ProviderCard } from './subcomponents/ProviderCard';
|
||||
import CardContainer from './subcomponents/CardContainer';
|
||||
import { ProviderModalProvider, useProviderModal } from './modal/ProviderModalProvider';
|
||||
import ProviderConfigurationModal from './modal/ProviderConfiguationModal';
|
||||
import { ProviderDetails } from '../../../api';
|
||||
import { ProviderDetails, CreateCustomProviderRequest } from '../../../api';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
|
||||
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
|
||||
|
||||
const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
@@ -18,6 +22,27 @@ const GridLayout = memo(function GridLayout({ children }: { children: React.Reac
|
||||
);
|
||||
});
|
||||
|
||||
const CustomProviderCard = memo(function CustomProviderCard({ onClick }: { onClick: () => void }) {
|
||||
return (
|
||||
<CardContainer
|
||||
testId="add-custom-provider-card"
|
||||
onClick={onClick}
|
||||
header={null}
|
||||
body={
|
||||
<div className="flex flex-col items-center justify-center min-h-[200px]">
|
||||
<Plus className="w-8 h-8 text-gray-400 mb-2" />
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 text-center">
|
||||
<div>Add</div>
|
||||
<div>Custom Provider</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
grayedOut={false}
|
||||
borderStyle="dashed"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
// Memoize the ProviderCards component
|
||||
const ProviderCards = memo(function ProviderCards({
|
||||
providers,
|
||||
@@ -31,6 +56,7 @@ const ProviderCards = memo(function ProviderCards({
|
||||
onProviderLaunch: (provider: ProviderDetails) => void;
|
||||
}) {
|
||||
const { openModal } = useProviderModal();
|
||||
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
|
||||
|
||||
// Memoize these functions so they don't get recreated on every render
|
||||
const configureProviderViaModal = useCallback(
|
||||
@@ -42,7 +68,7 @@ const ProviderCards = memo(function ProviderCards({
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
onDelete: () => {
|
||||
onDelete: (_values: unknown) => {
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
@@ -56,7 +82,7 @@ const ProviderCards = memo(function ProviderCards({
|
||||
const deleteProviderConfigViaModal = useCallback(
|
||||
(provider: ProviderDetails) => {
|
||||
openModal(provider, {
|
||||
onDelete: () => {
|
||||
onDelete: (_values: unknown) => {
|
||||
// Only refresh if the function is provided
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
@@ -68,12 +94,27 @@ const ProviderCards = memo(function ProviderCards({
|
||||
[openModal, refreshProviders]
|
||||
);
|
||||
|
||||
// We don't need an intermediate function here
|
||||
// Just pass the onProviderLaunch directly
|
||||
const handleCreateCustomProvider = useCallback(
|
||||
async (data: CreateCustomProviderRequest) => {
|
||||
try {
|
||||
const { createCustomProvider } = await import('../../../api');
|
||||
await createCustomProvider({ body: data });
|
||||
setShowCustomProviderModal(false);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create custom provider:', error);
|
||||
}
|
||||
},
|
||||
[refreshProviders]
|
||||
);
|
||||
|
||||
// Use useMemo to memoize the cards array
|
||||
const providerCards = useMemo(() => {
|
||||
return providers.map((provider) => (
|
||||
// providers needs to be an array
|
||||
const providersArray = Array.isArray(providers) ? providers : [];
|
||||
const cards = providersArray.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.name}
|
||||
provider={provider}
|
||||
@@ -83,6 +124,12 @@ const ProviderCards = memo(function ProviderCards({
|
||||
isOnboarding={isOnboarding}
|
||||
/>
|
||||
));
|
||||
|
||||
cards.push(
|
||||
<CustomProviderCard key="add-custom" onClick={() => setShowCustomProviderModal(true)} />
|
||||
);
|
||||
|
||||
return cards;
|
||||
}, [
|
||||
providers,
|
||||
isOnboarding,
|
||||
@@ -91,7 +138,23 @@ const ProviderCards = memo(function ProviderCards({
|
||||
onProviderLaunch,
|
||||
]);
|
||||
|
||||
return <>{providerCards}</>;
|
||||
return (
|
||||
<>
|
||||
{providerCards}
|
||||
|
||||
<Dialog open={showCustomProviderModal} onOpenChange={setShowCustomProviderModal}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Custom Provider</DialogTitle>
|
||||
</DialogHeader>
|
||||
<CustomProviderForm
|
||||
onSubmit={handleCreateCustomProvider}
|
||||
onCancel={() => setShowCustomProviderModal(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default memo(function ProviderGrid({
|
||||
|
||||
@@ -18,7 +18,7 @@ import OllamaForm from './subcomponents/forms/OllamaForm';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { ConfigKey } from '../../../../api';
|
||||
import { ConfigKey, removeCustomProvider } from '../../../../api';
|
||||
|
||||
interface FormValues {
|
||||
[key: string]: string | number | boolean | null;
|
||||
@@ -162,13 +162,21 @@ export default function ProviderConfigurationModal() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Remove the provider configuration
|
||||
// get the keys
|
||||
const params = currentProvider.metadata.config_keys;
|
||||
const isCustomProvider = currentProvider.name.startsWith('custom_');
|
||||
|
||||
// go through the keys are remove them
|
||||
for (const param of params) {
|
||||
await remove(param.name, param.secret);
|
||||
if (isCustomProvider) {
|
||||
await removeCustomProvider({
|
||||
path: { id: currentProvider.name },
|
||||
});
|
||||
} else {
|
||||
// Remove the provider configuration
|
||||
// get the keys
|
||||
const params = currentProvider.metadata.config_keys;
|
||||
|
||||
// go through the keys are remove them
|
||||
for (const param of params) {
|
||||
await remove(param.name, param.secret);
|
||||
}
|
||||
}
|
||||
|
||||
// Call onDelete callback if provided
|
||||
@@ -247,9 +255,9 @@ export default function ProviderConfigurationModal() {
|
||||
setShowDeleteConfirmation(false);
|
||||
setIsActiveProvider(false);
|
||||
}}
|
||||
canDelete={isConfigured && !isActiveProvider} // Disable delete button for active provider
|
||||
canDelete={isConfigured && !isActiveProvider}
|
||||
providerName={currentProvider.metadata.display_name}
|
||||
isActiveProvider={isActiveProvider} // Pass this to actions for button state
|
||||
isActiveProvider={isActiveProvider}
|
||||
/>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Input } from '../../../../../ui/input';
|
||||
import { Select } from '../../../../../ui/Select';
|
||||
import { Button } from '../../../../../ui/button';
|
||||
import { SecureStorageNotice } from '../SecureStorageNotice';
|
||||
import { Checkbox } from '@radix-ui/themes';
|
||||
|
||||
interface CustomProviderFormProps {
|
||||
onSubmit: (data: {
|
||||
provider_type: string;
|
||||
display_name: string;
|
||||
api_url: string;
|
||||
api_key: string;
|
||||
models: string[];
|
||||
supports_streaming: boolean;
|
||||
}) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function CustomProviderForm({ onSubmit, onCancel }: CustomProviderFormProps) {
|
||||
const [providerType, setProviderType] = useState('openai_compatible');
|
||||
const [displayName, setDisplayName] = useState('');
|
||||
const [apiUrl, setApiUrl] = useState('');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [models, setModels] = useState('');
|
||||
const [isLocalModel, setIsLocalModel] = useState(false);
|
||||
const [supportsStreaming, setSupportsStreaming] = useState(true);
|
||||
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const handleLocalModels = (checked: boolean) => {
|
||||
setIsLocalModel(checked);
|
||||
if (checked) {
|
||||
setApiKey('notrequired');
|
||||
} else {
|
||||
setApiKey('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const errors: Record<string, string> = {};
|
||||
if (!displayName) errors.displayName = 'Display name is required';
|
||||
if (!apiUrl) errors.apiUrl = 'API URL is required';
|
||||
if (!isLocalModel && !apiKey) errors.apiKey = 'API key is required';
|
||||
if (!models) errors.models = 'At least one model is required';
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setValidationErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const modelList = models
|
||||
.split(',')
|
||||
.map((m) => m.trim())
|
||||
.filter((m) => m);
|
||||
|
||||
onSubmit({
|
||||
provider_type: providerType,
|
||||
display_name: displayName,
|
||||
api_url: apiUrl,
|
||||
api_key: apiKey,
|
||||
models: modelList,
|
||||
supports_streaming: supportsStreaming,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="mt-4 space-y-4">
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white mb-1">
|
||||
Provider Type
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'openai_compatible', label: 'OpenAI Compatible' },
|
||||
{ value: 'anthropic_compatible', label: 'Anthropic Compatible' },
|
||||
{ value: 'ollama_compatible', label: 'Ollama Compatible' },
|
||||
]}
|
||||
value={{
|
||||
value: providerType,
|
||||
label:
|
||||
providerType === 'openai_compatible'
|
||||
? 'OpenAI Compatible'
|
||||
: providerType === 'anthropic_compatible'
|
||||
? 'Anthropic Compatible'
|
||||
: 'Ollama Compatible',
|
||||
}}
|
||||
onChange={(option: unknown) => {
|
||||
const selectedOption = option as { value: string; label: string } | null;
|
||||
if (selectedOption) setProviderType(selectedOption.value);
|
||||
}}
|
||||
isSearchable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white mb-1">
|
||||
Display Name
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="Your Provider Name"
|
||||
className={validationErrors.displayName ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.displayName && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors.displayName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white mb-1">
|
||||
API URL
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={apiUrl}
|
||||
onChange={(e) => setApiUrl(e.target.value)}
|
||||
placeholder="https://api.example.com/v1/messages"
|
||||
className={validationErrors.apiUrl ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.apiUrl && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors.apiUrl}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white mb-1">
|
||||
API Key
|
||||
{!isLocalModel && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<Input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="Your API key"
|
||||
className={validationErrors.apiKey ? 'border-red-500' : ''}
|
||||
disabled={isLocalModel}
|
||||
/>
|
||||
{validationErrors.apiKey && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors.apiKey}</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2 mt-2">
|
||||
<Checkbox id="local-model" checked={isLocalModel} onCheckedChange={handleLocalModels} />
|
||||
<label
|
||||
htmlFor="local-model"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-400"
|
||||
>
|
||||
This is a local model (no auth required)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white mb-1">
|
||||
Available Models (comma-separated)
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={models}
|
||||
onChange={(e) => setModels(e.target.value)}
|
||||
placeholder="model-a, model-b, model-c"
|
||||
className={validationErrors.models ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.models && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors.models}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 mb-10">
|
||||
<Checkbox
|
||||
id="supports-streaming"
|
||||
checked={supportsStreaming}
|
||||
onCheckedChange={(checked) => setSupportsStreaming(checked as boolean)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="supports-streaming"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-400"
|
||||
>
|
||||
Provider supports streaming responses
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<SecureStorageNotice />
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Create Provider</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
+63
-36
@@ -3,9 +3,7 @@ import { Input } from '../../../../../ui/input';
|
||||
import { useConfig } from '../../../../../ConfigContext'; // Adjust this import path as needed
|
||||
import { ProviderDetails, ConfigKey } from '../../../../../../api';
|
||||
|
||||
interface ValidationErrors {
|
||||
[key: string]: string;
|
||||
}
|
||||
type ValidationErrors = Record<string, string>;
|
||||
|
||||
interface DefaultProviderSetupFormProps {
|
||||
configValues: Record<string, string>;
|
||||
@@ -36,33 +34,30 @@ export default function DefaultProviderSetupForm({
|
||||
|
||||
// Try to load actual values from config for each parameter that is not secret
|
||||
for (const parameter of parameters) {
|
||||
if (parameter.required) {
|
||||
try {
|
||||
// Check if there's a stored value in the config system
|
||||
const configKey = `${parameter.name}`;
|
||||
const configResponse = await read(configKey, parameter.secret || false);
|
||||
try {
|
||||
// Check if there's a stored value in the config system
|
||||
const configKey = `${parameter.name}`;
|
||||
const configResponse = await read(configKey, parameter.secret || false);
|
||||
|
||||
if (configResponse) {
|
||||
// Use the value from the config provider
|
||||
newValues[parameter.name] = String(configResponse);
|
||||
} else if (
|
||||
parameter.default !== undefined &&
|
||||
parameter.default !== null &&
|
||||
!configValues[parameter.name]
|
||||
) {
|
||||
// Fall back to default value if no config value exists
|
||||
newValues[parameter.name] = String(parameter.default);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load config for ${parameter.name}:`, error);
|
||||
// Fall back to default if read operation fails
|
||||
if (
|
||||
parameter.default !== undefined &&
|
||||
parameter.default !== null &&
|
||||
!configValues[parameter.name]
|
||||
) {
|
||||
newValues[parameter.name] = String(parameter.default);
|
||||
}
|
||||
if (configResponse) {
|
||||
newValues[parameter.name] = parameter.secret ? 'true' : String(configResponse);
|
||||
} else if (
|
||||
parameter.default !== undefined &&
|
||||
parameter.default !== null &&
|
||||
!configValues[parameter.name]
|
||||
) {
|
||||
// Fall back to default value if no config value exists
|
||||
newValues[parameter.name] = String(parameter.default);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load config for ${parameter.name}:`, error);
|
||||
// Fall back to default if read operation fails
|
||||
if (
|
||||
parameter.default !== undefined &&
|
||||
parameter.default !== null &&
|
||||
!configValues[parameter.name]
|
||||
) {
|
||||
newValues[parameter.name] = String(parameter.default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +80,11 @@ export default function DefaultProviderSetupForm({
|
||||
return parameters.filter((param) => param.required === true);
|
||||
}, [parameters]);
|
||||
|
||||
// TODO: show all params, not just required ones
|
||||
// const allParameters = useMemo(() => {
|
||||
// return parameters;
|
||||
// }, [parameters]);
|
||||
|
||||
// Helper function to generate appropriate placeholder text
|
||||
const getPlaceholder = (parameter: ConfigKey): string => {
|
||||
// If default is defined and not null, show it
|
||||
@@ -92,8 +92,30 @@ export default function DefaultProviderSetupForm({
|
||||
return `Default: ${parameter.default}`;
|
||||
}
|
||||
|
||||
// Otherwise, use the parameter name as a hint
|
||||
return parameter.name.toUpperCase();
|
||||
const name = parameter.name.toLowerCase();
|
||||
if (name.includes('api_key')) return 'Your API key';
|
||||
if (name.includes('api_url') || name.includes('host')) return 'https://api.example.com';
|
||||
if (name.includes('models')) return 'model-a, model-b';
|
||||
|
||||
return parameter.name
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, (str) => str.toUpperCase())
|
||||
.trim();
|
||||
};
|
||||
|
||||
// helper for custom labels
|
||||
const getFieldLabel = (parameter: ConfigKey): string => {
|
||||
const name = parameter.name.toLowerCase();
|
||||
if (name.includes('api_key')) return 'API Key';
|
||||
if (name.includes('api_url') || name.includes('host')) return 'API Host';
|
||||
if (name.includes('models')) return 'Models';
|
||||
|
||||
return parameter.name
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, (str) => str.toUpperCase())
|
||||
.trim();
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
@@ -111,25 +133,30 @@ export default function DefaultProviderSetupForm({
|
||||
requiredParameters.map((parameter) => (
|
||||
<div key={parameter.name}>
|
||||
<label className="block text-sm font-medium text-textStandard mb-1">
|
||||
{parameter.name}
|
||||
{getFieldLabel(parameter)}
|
||||
{parameter.required && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<Input
|
||||
type={parameter.secret ? 'password' : 'text'}
|
||||
value={configValues[parameter.name] || ''}
|
||||
onChange={(e) =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
console.log(`Setting ${parameter.name} to:`, e.target.value);
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[parameter.name]: e.target.value,
|
||||
}))
|
||||
}
|
||||
}));
|
||||
}}
|
||||
placeholder={getPlaceholder(parameter)}
|
||||
className={`w-full h-14 px-4 font-regular rounded-lg shadow-none ${
|
||||
validationErrors[parameter.name]
|
||||
? 'border-2 border-red-500'
|
||||
: 'border border-borderSubtle hover:border-borderStandard'
|
||||
} bg-background-default text-lg placeholder:text-textSubtle font-regular text-textStandard`}
|
||||
required={true}
|
||||
required={parameter.required}
|
||||
/>
|
||||
{validationErrors[parameter.name] && (
|
||||
<p className="text-red-500 text-sm mt-1">{validationErrors[parameter.name]}</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ interface CardContainerProps {
|
||||
onClick: () => void;
|
||||
grayedOut: boolean;
|
||||
testId?: string;
|
||||
borderStyle?: 'solid' | 'dashed';
|
||||
}
|
||||
|
||||
function GlowingRing() {
|
||||
@@ -33,6 +34,7 @@ export default function CardContainer({
|
||||
onClick,
|
||||
grayedOut = false,
|
||||
testId,
|
||||
borderStyle = 'solid',
|
||||
}: CardContainerProps) {
|
||||
return (
|
||||
<div
|
||||
@@ -50,19 +52,21 @@ export default function CardContainer({
|
||||
>
|
||||
{!grayedOut && <GlowingRing />}
|
||||
<div
|
||||
className={`relative bg-background-default rounded-lg p-3 transition-all duration-200 h-[160px] flex flex-col justify-between
|
||||
className={`relative bg-background-default rounded-lg p-3 transition-all duration-200 h-[160px] flex flex-col
|
||||
${header ? 'justify-between' : 'justify-center'}
|
||||
${borderStyle === 'dashed' ? 'border-2 border-dashed' : 'border'}
|
||||
${
|
||||
grayedOut
|
||||
? 'border border-borderSubtle'
|
||||
: 'border border-borderSubtle hover:border-borderStandard'
|
||||
? 'border-borderSubtle'
|
||||
: 'border-borderSubtle hover:border-borderStandard'
|
||||
}`}
|
||||
>
|
||||
{/* Apply opacity only to the header when grayed out */}
|
||||
<div style={{ opacity: grayedOut ? '0.5' : '1' }}>
|
||||
<HeaderContainer>{header}</HeaderContainer>
|
||||
</div>
|
||||
{header && (
|
||||
<div style={{ opacity: grayedOut ? '0.5' : '1' }}>
|
||||
<HeaderContainer>{header}</HeaderContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Body always at full opacity */}
|
||||
<div>{body}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '../../utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[1px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[1px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Message } from './types/message';
|
||||
import { getSessionHistory, listSessions, SessionInfo } from './api';
|
||||
import { getSessionHistory, listSessions, SessionInfo, Message as ApiMessage } from './api';
|
||||
import { convertApiMessageToFrontendMessage } from './components/context_management';
|
||||
import { getApiUrl } from './config';
|
||||
|
||||
@@ -118,7 +118,7 @@ export async function fetchSessionDetails(sessionId: string): Promise<SessionDet
|
||||
return {
|
||||
session_id: response.data.sessionId,
|
||||
metadata: ensureWorkingDir(response.data.metadata),
|
||||
messages: response.data.messages.map((message) =>
|
||||
messages: response.data.messages.map((message: ApiMessage) =>
|
||||
convertApiMessageToFrontendMessage(message, true, true)
|
||||
), // slight diffs between backend and frontend Message obj
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user