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

@@ -11,6 +11,7 @@ pub struct Model {
pub sort_order: i32,
pub installed_version: Option<String>,
pub env_json: Option<String>,
pub model_provider_id: Option<i32>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
}

View File

@@ -0,0 +1,35 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(AgentSetting::Table)
.add_column(ColumnDef::new(AgentSetting::ModelProviderId).integer().null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(AgentSetting::Table)
.drop_column(AgentSetting::ModelProviderId)
.to_owned(),
)
.await
}
}
#[derive(DeriveIden)]
enum AgentSetting {
Table,
ModelProviderId,
}

View File

@@ -8,6 +8,7 @@ mod m20260227_000001_folder_parent_branch;
mod m20260330_000001_chat_channel;
mod m20260401_000001_chat_channel_sender_context;
mod m20260404_000001_model_provider;
mod m20260406_000001_agent_setting_model_provider;
pub struct Migrator;
#[async_trait::async_trait]
@@ -22,6 +23,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260330_000001_chat_channel::Migration),
Box::new(m20260401_000001_chat_channel_sender_context::Migration),
Box::new(m20260404_000001_model_provider::Migration),
Box::new(m20260406_000001_agent_setting_model_provider::Migration),
]
}
}

View File

@@ -22,6 +22,7 @@ pub struct AgentDefaultInput {
pub struct AgentSettingsUpdate {
pub enabled: bool,
pub env_json: Option<String>,
pub model_provider_id: Option<i32>,
}
fn default_enabled(agent_type: AgentType) -> bool {
@@ -67,6 +68,7 @@ pub async fn ensure_defaults(
sort_order: Set(default.default_sort_order),
installed_version: Set(None),
env_json: Set(None),
model_provider_id: Set(None),
created_at: Set(now),
updated_at: Set(now),
};
@@ -133,6 +135,7 @@ pub async fn update(
let mut active = model.into_active_model();
active.enabled = Set(patch.enabled);
active.env_json = Set(patch.env_json);
active.model_provider_id = Set(patch.model_provider_id);
active.updated_at = Set(Utc::now());
active.update(conn).await?;
Ok(())
@@ -202,6 +205,25 @@ async fn reorder_once(conn: &DatabaseConnection, agent_types: &[AgentType]) -> R
Ok(())
}
pub async fn clear_model_provider_id(
conn: &DatabaseConnection,
model_provider_id: i32,
) -> Result<(), DbError> {
let rows = agent_setting::Entity::find()
.all(conn)
.await?;
let now = Utc::now();
for row in rows {
if row.model_provider_id == Some(model_provider_id) {
let mut active = row.into_active_model();
active.model_provider_id = Set(None);
active.updated_at = Set(now);
active.update(conn).await?;
}
}
Ok(())
}
fn is_sqlite_full_error(err: &DbError) -> bool {
let message = err.to_string();
message.contains("database or disk is full") || message.contains("(code: 13)")