feat: added providers management settings with ability to add or remove providers (#76)
* feat: implement adding opencode providers in openchamber settings * feat: implement provider authentication management with removal functionality
This commit is contained in:
committed by
GitHub
parent
ff874cc33d
commit
8f9facb561
@@ -4,6 +4,7 @@ mod commands;
|
||||
mod logging;
|
||||
mod assistant_notifications;
|
||||
mod session_activity;
|
||||
mod opencode_auth;
|
||||
mod opencode_config;
|
||||
mod opencode_manager;
|
||||
mod window_state;
|
||||
@@ -1493,6 +1494,58 @@ async fn handle_config_routes(
|
||||
));
|
||||
}
|
||||
|
||||
// Handle provider auth removal: DELETE /api/provider/:providerId/auth
|
||||
if let Some(rest) = path.strip_prefix("/api/provider/") {
|
||||
if let Some(provider_id) = rest.strip_suffix("/auth") {
|
||||
if method == Method::DELETE {
|
||||
let trimmed = provider_id.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(config_error_response(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Provider ID is required",
|
||||
));
|
||||
}
|
||||
|
||||
match opencode_auth::remove_provider_auth(trimmed).await {
|
||||
Ok(removed) => {
|
||||
if let Err(resp) = refresh_opencode_after_config_change(
|
||||
&state,
|
||||
&format!("provider {} disconnected", trimmed),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Ok(resp);
|
||||
}
|
||||
|
||||
return Ok(json_response(
|
||||
StatusCode::OK,
|
||||
ConfigActionResponse {
|
||||
success: true,
|
||||
requires_reload: true,
|
||||
message: if removed {
|
||||
"Provider disconnected successfully".to_string()
|
||||
} else {
|
||||
"Provider was not connected".to_string()
|
||||
},
|
||||
reload_delay_ms: CLIENT_RELOAD_DELAY_MS,
|
||||
},
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"[desktop:config] Failed to disconnect provider {}: {}",
|
||||
trimmed, err
|
||||
);
|
||||
return Ok(config_error_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
|
||||
@@ -1585,9 +1638,16 @@ async fn proxy_to_opencode(
|
||||
let origin_path = original.0.path().to_string();
|
||||
let method = req.method().clone();
|
||||
|
||||
// Check if this is a provider auth deletion request (DELETE /api/provider/:id/auth)
|
||||
let is_provider_auth_delete = method == Method::DELETE
|
||||
&& origin_path.starts_with("/api/provider/")
|
||||
&& origin_path.ends_with("/auth")
|
||||
&& origin_path != "/api/provider/auth"; // Exclude GET /api/provider/auth
|
||||
|
||||
let is_desktop_config_route = origin_path.starts_with("/api/config/agents/")
|
||||
|| origin_path.starts_with("/api/config/commands/")
|
||||
|| origin_path == "/api/config/reload";
|
||||
|| origin_path == "/api/config/reload"
|
||||
|| is_provider_auth_delete;
|
||||
|
||||
if is_desktop_config_route {
|
||||
return handle_config_routes(state, &origin_path, method, req).await;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::info;
|
||||
use serde_json::Value;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
|
||||
/// Get OpenCode data directory path (~/.local/share/opencode)
|
||||
fn get_data_dir() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Cannot determine home directory")
|
||||
.join(".local")
|
||||
.join("share")
|
||||
.join("opencode")
|
||||
}
|
||||
|
||||
/// Get auth file path
|
||||
fn get_auth_file() -> PathBuf {
|
||||
get_data_dir().join("auth.json")
|
||||
}
|
||||
|
||||
/// Ensure data directory exists
|
||||
async fn ensure_data_dir() -> Result<()> {
|
||||
let data_dir = get_data_dir();
|
||||
fs::create_dir_all(&data_dir).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read auth.json file
|
||||
pub async fn read_auth() -> Result<Value> {
|
||||
let auth_file = get_auth_file();
|
||||
|
||||
if !auth_file.exists() {
|
||||
return Ok(Value::Object(serde_json::Map::new()));
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&auth_file).await?;
|
||||
let trimmed = content.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return Ok(Value::Object(serde_json::Map::new()));
|
||||
}
|
||||
|
||||
serde_json::from_str(trimmed).map_err(|e| anyhow!("Failed to parse auth file: {}", e))
|
||||
}
|
||||
|
||||
/// Write auth.json file with backup
|
||||
pub async fn write_auth(auth: &Value) -> Result<()> {
|
||||
ensure_data_dir().await?;
|
||||
|
||||
let auth_file = get_auth_file();
|
||||
|
||||
// Create backup before writing
|
||||
if auth_file.exists() {
|
||||
let file_name = auth_file
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| anyhow!("Invalid auth file name"))?;
|
||||
|
||||
let backup_path = auth_file.with_file_name(format!("{file_name}.openchamber.backup"));
|
||||
fs::copy(&auth_file, &backup_path).await?;
|
||||
info!("Created auth backup: {}", backup_path.display());
|
||||
}
|
||||
|
||||
let json_string = serde_json::to_string_pretty(auth)?;
|
||||
fs::write(&auth_file, json_string).await?;
|
||||
info!("Successfully wrote auth file");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove provider auth entry from auth.json
|
||||
pub async fn remove_provider_auth(provider_id: &str) -> Result<bool> {
|
||||
if provider_id.is_empty() {
|
||||
return Err(anyhow!("Provider ID is required"));
|
||||
}
|
||||
|
||||
let mut auth = read_auth().await?;
|
||||
|
||||
let auth_obj = auth
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| anyhow!("Auth file is not a valid JSON object"))?;
|
||||
|
||||
if !auth_obj.contains_key(provider_id) {
|
||||
info!(
|
||||
"Provider {} not found in auth file, nothing to remove",
|
||||
provider_id
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
auth_obj.remove(provider_id);
|
||||
write_auth(&auth).await?;
|
||||
info!("Removed provider auth: {}", provider_id);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Get provider auth entry
|
||||
pub async fn get_provider_auth(provider_id: &str) -> Result<Option<Value>> {
|
||||
let auth = read_auth().await?;
|
||||
|
||||
Ok(auth
|
||||
.as_object()
|
||||
.and_then(|obj| obj.get(provider_id))
|
||||
.cloned())
|
||||
}
|
||||
|
||||
/// List all provider IDs with auth
|
||||
pub async fn list_provider_auths() -> Result<Vec<String>> {
|
||||
let auth = read_auth().await?;
|
||||
|
||||
Ok(auth
|
||||
.as_object()
|
||||
.map(|obj| obj.keys().cloned().collect())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
Reference in New Issue
Block a user