mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-03-28 14:13:40 +08:00
fix(opencode): prevent config nesting and use slugified provider IDs
- Skip backfill logic for OpenCode (additive mode doesn't need it) - Add defensive check in write_live_snapshot to extract provider fragment - Use slugified name as provider ID for readable config keys
This commit is contained in:
@@ -125,9 +125,29 @@ pub(crate) fn write_live_snapshot(app_type: &AppType, provider: &Provider) -> Re
|
||||
use crate::opencode_config;
|
||||
use crate::provider::OpenCodeProviderConfig;
|
||||
|
||||
// Defensive check: if settings_config is a full config structure, extract provider fragment
|
||||
let config_to_write = if let Some(obj) = provider.settings_config.as_object() {
|
||||
// Detect full config structure (has $schema or top-level provider field)
|
||||
if obj.contains_key("$schema") || obj.contains_key("provider") {
|
||||
log::warn!(
|
||||
"OpenCode provider '{}' has full config structure in settings_config, attempting to extract fragment",
|
||||
provider.id
|
||||
);
|
||||
// Try to extract from provider.{id}
|
||||
obj.get("provider")
|
||||
.and_then(|p| p.get(&provider.id))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| provider.settings_config.clone())
|
||||
} else {
|
||||
provider.settings_config.clone()
|
||||
}
|
||||
} else {
|
||||
provider.settings_config.clone()
|
||||
};
|
||||
|
||||
// Convert settings_config to OpenCodeProviderConfig
|
||||
let opencode_config_result =
|
||||
serde_json::from_value::<OpenCodeProviderConfig>(provider.settings_config.clone());
|
||||
serde_json::from_value::<OpenCodeProviderConfig>(config_to_write.clone());
|
||||
|
||||
match opencode_config_result {
|
||||
Ok(config) => {
|
||||
@@ -140,8 +160,21 @@ pub(crate) fn write_live_snapshot(app_type: &AppType, provider: &Provider) -> Re
|
||||
provider.id,
|
||||
e
|
||||
);
|
||||
// Still try to write as raw JSON
|
||||
opencode_config::set_provider(&provider.id, provider.settings_config.clone())?;
|
||||
// Only write if config looks like a valid provider fragment
|
||||
if config_to_write.get("npm").is_some()
|
||||
|| config_to_write.get("options").is_some()
|
||||
{
|
||||
opencode_config::set_provider(&provider.id, config_to_write)?;
|
||||
log::info!(
|
||||
"OpenCode provider '{}' written as raw JSON to live config",
|
||||
provider.id
|
||||
);
|
||||
} else {
|
||||
log::error!(
|
||||
"OpenCode provider '{}' has invalid config structure, skipping write",
|
||||
provider.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,12 +351,16 @@ impl ProviderService {
|
||||
|
||||
if let Some(current_id) = current_id {
|
||||
if current_id != id {
|
||||
// Only backfill when switching to a different provider
|
||||
if let Ok(live_config) = read_live_settings(app_type.clone()) {
|
||||
if let Some(mut current_provider) = providers.get(¤t_id).cloned() {
|
||||
current_provider.settings_config = live_config;
|
||||
// Ignore backfill failure, don't affect switch flow
|
||||
let _ = state.db.save_provider(app_type.as_str(), ¤t_provider);
|
||||
// OpenCode uses additive mode - all providers coexist in the same file,
|
||||
// no backfill needed (backfill is for exclusive mode apps like Claude/Codex/Gemini)
|
||||
if !matches!(app_type, AppType::OpenCode) {
|
||||
// Only backfill when switching to a different provider
|
||||
if let Ok(live_config) = read_live_settings(app_type.clone()) {
|
||||
if let Some(mut current_provider) = providers.get(¤t_id).cloned() {
|
||||
current_provider.settings_config = live_config;
|
||||
// Ignore backfill failure, don't affect switch flow
|
||||
let _ = state.db.save_provider(app_type.as_str(), ¤t_provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,34 @@ import type { Provider, Settings } from "@/types";
|
||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||
import { generateUUID } from "@/utils/uuid";
|
||||
|
||||
/**
|
||||
* Convert a name to a URL-safe slug for use as provider ID
|
||||
* Used for OpenCode's additive mode where ID becomes the config key
|
||||
*/
|
||||
function slugify(name: string): string {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[\s_]+/g, "-") // spaces and underscores → hyphens
|
||||
.replace(/[^a-z0-9-]/g, "") // remove special characters
|
||||
.replace(/-+/g, "-") // collapse multiple hyphens
|
||||
.replace(/^-|-$/g, ""); // trim leading/trailing hyphens
|
||||
}
|
||||
|
||||
export const useAddProviderMutation = (appId: AppId) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (providerInput: Omit<Provider, "id">) => {
|
||||
// OpenCode: use slugified name as ID (for readable config keys)
|
||||
// Other apps: use random UUID
|
||||
const id =
|
||||
appId === "opencode" ? slugify(providerInput.name) : generateUUID();
|
||||
|
||||
const newProvider: Provider = {
|
||||
...providerInput,
|
||||
id: generateUUID(),
|
||||
id,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
await providersApi.add(newProvider, appId);
|
||||
|
||||
Reference in New Issue
Block a user