feat(settings): refactor agent auth modes and add model provider authentication

- Split env vars and config file persistence into separate save operations
- Add model_provider_id field to agent_setting for tracking selected provider
- Add "Model Provider" auth mode for Claude Code, Codex CLI, and Gemini CLI
- Add "Custom Endpoint" auth mode for Claude Code (previously only official subscription)
- Unify auth mode labels across all three agents (official subscription / custom endpoint / model provider)
- When model provider is selected, fill api_url and api_key into env and config automatically
- Resolve model provider credentials at ACP connect time as a backend fallback
- Clear provider deletion cascades to agent_setting.model_provider_id
- Claude Code writes API credentials to config.env (ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN)
- Codex: switching auth modes patches config.toml instead of clearing it
- Add i18n keys for new auth modes in all 10 supported languages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-06 22:10:18 +08:00
parent d28c54a038
commit b64976e4d6
24 changed files with 1269 additions and 276 deletions

View File

@@ -80,6 +80,15 @@ pub async fn acp_connect(
local_config_json.as_deref(),
);
// Resolve model provider credentials if configured.
acp_commands::apply_model_provider_env(
params.agent_type,
setting.as_ref(),
&mut runtime_env,
&db.conn,
)
.await;
if params.agent_type == AgentType::OpenClaw && params.session_id.is_none() {
runtime_env.insert("OPENCLAW_RESET_SESSION".into(), "1".into());
}
@@ -398,6 +407,62 @@ pub async fn acp_update_agent_preferences(
Ok(Json(()))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AcpUpdateAgentEnvParams {
pub agent_type: AgentType,
pub enabled: bool,
pub env: BTreeMap<String, String>,
pub model_provider_id: Option<i32>,
}
pub async fn acp_update_agent_env(
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpUpdateAgentEnvParams>,
) -> Result<Json<()>, AppCommandError> {
let db = &state.db;
let emitter = state.emitter.clone();
acp_commands::acp_update_agent_env_core(
params.agent_type,
params.enabled,
params.env,
params.model_provider_id,
db,
&emitter,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(()))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AcpUpdateAgentConfigParams {
pub agent_type: AgentType,
pub config_json: Option<String>,
pub opencode_auth_json: Option<String>,
pub codex_auth_json: Option<String>,
pub codex_config_toml: Option<String>,
}
pub async fn acp_update_agent_config(
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpUpdateAgentConfigParams>,
) -> Result<Json<()>, AppCommandError> {
let emitter = state.emitter.clone();
acp_commands::acp_update_agent_config_core(
params.agent_type,
params.config_json,
params.opencode_auth_json,
params.codex_auth_json,
params.codex_config_toml,
&emitter,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(()))
}
pub async fn acp_download_agent_binary(
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AgentTypeParams>,