feat: implémentation et QA ticket99 - agent model configuration v2
- backend A-C: VO Codex/Claude, renderers/projecteurs modèle, SecretRef/env, catalogues/use cases/Tauri commands - frontend D: profils Codex/Claude provider->model->secret, validations, conservation SecretRef - fix: app-tauri embedded_server isolant IDEA_WEB_ROOT QA: backend 57/57 tests, frontend 74/74 tests OK
This commit is contained in:
@ -40,26 +40,27 @@ use crate::dto::{
|
||||
parse_ticket_id, save_model_server_input, AgentDriftListDto, AgentDto, AgentListDto,
|
||||
AppExitWorkGuardStateDto, AssignSkillRequestDto, AttachLiveAgentRequestDto,
|
||||
AttachLiveAgentResponseDto, BackgroundTaskDto, ChangeAgentProfileDto,
|
||||
ChangeAgentProfileRequestDto, CloneOpenCodeProfileFromSeedRequestDto,
|
||||
ConfigureProfilesRequestDto, ConversationDetailsDto, CreateAgentFromTemplateRequestDto,
|
||||
CreateAgentRequestDto, CreateLayoutRequestDto, CreateLayoutResultDto, CreateMemoryRequestDto,
|
||||
CreateProjectRequestDto, CreateSkillRequestDto, CreateTemplateRequestDto,
|
||||
DeleteLayoutRequestDto, DeleteLayoutResultDto, DeliveredDelegationRequestDto,
|
||||
DetectProfilesRequestDto, DetectProfilesResponseDto, EffectivePermissionsDto,
|
||||
EmbedderEnginesDto, EmbedderProfileDto, EmbedderProfileListDto, ErrorDto, FirstRunStateDto,
|
||||
FrontAttachedRequestDto, GitBranchesDto, GitCheckoutRequestDto, GitCommitDto, GitCommitListDto,
|
||||
GitCommitRequestDto, GitStageRequestDto, GitStatusListDto, GraphCommitListDto,
|
||||
HealthRequestDto, HealthResponseDto, InspectConversationRequestDto, InterruptAgentRequestDto,
|
||||
LaunchAgentRequestDto, LayoutDto, LayoutOperationDto, ListLayoutsDto, LiveAgentListDto,
|
||||
MemoryDto, MemoryIndexDto, MemoryLinksDto, MemoryListDto, ModelServerConfigDto,
|
||||
ModelServerConfigListDto, OpenCodeProviderListDto, OpenTerminalRequestDto,
|
||||
PreviewModelServerCommandDto, ProfileDto, ProfileListDto, ProjectDto, ProjectListDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectSystemPermissionsDto,
|
||||
ProjectWorkStateDto, ReadAgentContextResponseDto, ReadConversationPageRequestDto,
|
||||
ReattachChatDto, ReattachResultDto, RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk,
|
||||
ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
ResumableAgentListDto, SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
ChangeAgentProfileRequestDto, ClaudeProviderListDto, CloneOpenCodeProfileFromSeedRequestDto,
|
||||
CodexProviderListDto, ConfigureProfilesRequestDto, ConversationDetailsDto,
|
||||
CreateAgentFromTemplateRequestDto, CreateAgentRequestDto, CreateLayoutRequestDto,
|
||||
CreateLayoutResultDto, CreateMemoryRequestDto, CreateProjectRequestDto, CreateSkillRequestDto,
|
||||
CreateTemplateRequestDto, DeleteLayoutRequestDto, DeleteLayoutResultDto,
|
||||
DeliveredDelegationRequestDto, DetectProfilesRequestDto, DetectProfilesResponseDto,
|
||||
EffectivePermissionsDto, EmbedderEnginesDto, EmbedderProfileDto, EmbedderProfileListDto,
|
||||
ErrorDto, FirstRunStateDto, FrontAttachedRequestDto, GitBranchesDto, GitCheckoutRequestDto,
|
||||
GitCommitDto, GitCommitListDto, GitCommitRequestDto, GitStageRequestDto, GitStatusListDto,
|
||||
GraphCommitListDto, HealthRequestDto, HealthResponseDto, InspectConversationRequestDto,
|
||||
InterruptAgentRequestDto, LaunchAgentRequestDto, LayoutDto, LayoutOperationDto, ListLayoutsDto,
|
||||
LiveAgentListDto, MemoryDto, MemoryIndexDto, MemoryLinksDto, MemoryListDto,
|
||||
ModelServerConfigDto, ModelServerConfigListDto, OpenCodeProviderListDto,
|
||||
OpenTerminalRequestDto, PreviewModelServerCommandDto, ProfileDto, ProfileListDto, ProjectDto,
|
||||
ProjectListDto, ProjectMcpToolPermissionsDto, ProjectPermissionsDto,
|
||||
ProjectSystemPermissionsDto, ProjectWorkStateDto, ReadAgentContextResponseDto,
|
||||
ReadConversationPageRequestDto, ReattachChatDto, ReattachResultDto, RecallMemoryRequestDto,
|
||||
RenameLayoutRequestDto, ReplyChunk, ResizeTerminalRequestDto,
|
||||
ResolveAgentPermissionsRequestDto, ResolveAgentSystemPermissionsRequestDto,
|
||||
ResolvedAgentSystemPermissionsDto, ResumableAgentListDto, SaveClaudeProviderProfileRequestDto,
|
||||
SaveCodexProviderProfileRequestDto, SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
SaveOpenCodeProviderProfileRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto,
|
||||
SetActiveLayoutResultDto, SkillDto, SkillListDto, StopLiveAgentRequestDto,
|
||||
StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
|
||||
@ -1188,6 +1189,22 @@ pub async fn list_opencode_providers(
|
||||
Ok(state.list_opencode_providers.execute().into())
|
||||
}
|
||||
|
||||
/// `list_codex_providers` — static catalogue of Codex providers (ticket #99).
|
||||
#[tauri::command]
|
||||
pub async fn list_codex_providers(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<CodexProviderListDto, ErrorDto> {
|
||||
Ok(state.list_codex_providers.execute().into())
|
||||
}
|
||||
|
||||
/// `list_claude_providers` — static catalogue of Claude providers (ticket #99).
|
||||
#[tauri::command]
|
||||
pub async fn list_claude_providers(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ClaudeProviderListDto, ErrorDto> {
|
||||
Ok(state.list_claude_providers.execute().into())
|
||||
}
|
||||
|
||||
/// `save_opencode_provider_profile` — create or replace an OpenCode profile
|
||||
/// backed by a cloud provider (ticket #92, lot B3). The literal API key is
|
||||
/// sealed into the `SecretStore`, never persisted in `profiles.json`.
|
||||
@ -1208,6 +1225,36 @@ pub async fn save_opencode_provider_profile(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `save_codex_provider_profile` — create or replace a Codex profile backed by
|
||||
/// a provider/model. The literal API key is sealed into the `SecretStore`.
|
||||
#[tauri::command]
|
||||
pub async fn save_codex_provider_profile(
|
||||
request: SaveCodexProviderProfileRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProfileDto, ErrorDto> {
|
||||
state
|
||||
.save_codex_provider_profile
|
||||
.execute(request.into())
|
||||
.await
|
||||
.map(ProfileDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `save_claude_provider_profile` — create or replace a Claude profile backed by
|
||||
/// a provider/model. The literal API key is sealed into the `SecretStore`.
|
||||
#[tauri::command]
|
||||
pub async fn save_claude_provider_profile(
|
||||
request: SaveClaudeProviderProfileRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProfileDto, ErrorDto> {
|
||||
state
|
||||
.save_claude_provider_profile
|
||||
.execute(request.into())
|
||||
.await
|
||||
.map(ProfileDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `clone_opencode_profile_from_seed` — create a new OpenCode profile instance
|
||||
/// from the canonical `opencode-llamacpp` seed/template.
|
||||
///
|
||||
|
||||
@ -623,29 +623,6 @@ mod tests {
|
||||
use super::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct EnvVarGuard {
|
||||
key: &'static str,
|
||||
previous: Option<std::ffi::OsString>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
fn set(key: &'static str, value: &Path) -> Self {
|
||||
let previous = std::env::var_os(key);
|
||||
std::env::set_var(key, value);
|
||||
Self { key, previous }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(previous) = &self.previous {
|
||||
std::env::set_var(self.key, previous);
|
||||
} else {
|
||||
std::env::remove_var(self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tmp_app_data() -> PathBuf {
|
||||
std::env::temp_dir().join(format!("idea-embedded-server-test-{}", Uuid::new_v4()))
|
||||
}
|
||||
@ -662,6 +639,19 @@ mod tests {
|
||||
web_root
|
||||
}
|
||||
|
||||
fn tmp_resource_dir_with_web_root() -> PathBuf {
|
||||
let resource_dir =
|
||||
std::env::temp_dir().join(format!("idea-embedded-server-resource-{}", Uuid::new_v4()));
|
||||
let web_root = resource_dir.join("web");
|
||||
std::fs::create_dir_all(&web_root).unwrap();
|
||||
std::fs::write(
|
||||
web_root.join("index.html"),
|
||||
"<!doctype html><title>IdeA</title>",
|
||||
)
|
||||
.unwrap();
|
||||
resource_dir
|
||||
}
|
||||
|
||||
fn loopback_bind_available() -> bool {
|
||||
std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).is_ok()
|
||||
}
|
||||
@ -806,9 +796,9 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
let app_data = tmp_app_data();
|
||||
let web_root = tmp_web_root();
|
||||
let _env = EnvVarGuard::set("IDEA_WEB_ROOT", &web_root);
|
||||
let controller = EmbeddedServerController::new(app_data.clone());
|
||||
let resource_dir = tmp_resource_dir_with_web_root();
|
||||
let controller =
|
||||
EmbeddedServerController::with_resource_dir(app_data.clone(), Some(resource_dir));
|
||||
controller
|
||||
.save_settings(ServerExposureSettingsDto {
|
||||
mode: ServerExposureMode::LocalOnly,
|
||||
@ -920,9 +910,9 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
let app_data = tmp_app_data();
|
||||
let web_root = tmp_web_root();
|
||||
let _env = EnvVarGuard::set("IDEA_WEB_ROOT", &web_root);
|
||||
let controller = EmbeddedServerController::new(app_data.clone());
|
||||
let resource_dir = tmp_resource_dir_with_web_root();
|
||||
let controller =
|
||||
EmbeddedServerController::with_resource_dir(app_data.clone(), Some(resource_dir));
|
||||
controller
|
||||
.save_settings(ServerExposureSettingsDto {
|
||||
mode: ServerExposureMode::LocalOnly,
|
||||
@ -979,9 +969,9 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
let app_data = tmp_app_data();
|
||||
let web_root = tmp_web_root();
|
||||
let _env = EnvVarGuard::set("IDEA_WEB_ROOT", &web_root);
|
||||
let controller = EmbeddedServerController::new(app_data.clone());
|
||||
let resource_dir = tmp_resource_dir_with_web_root();
|
||||
let controller =
|
||||
EmbeddedServerController::with_resource_dir(app_data.clone(), Some(resource_dir));
|
||||
controller
|
||||
.save_settings(ServerExposureSettingsDto {
|
||||
mode: ServerExposureMode::LocalOnly,
|
||||
@ -1006,11 +996,11 @@ mod tests {
|
||||
return;
|
||||
}
|
||||
let app_data = tmp_app_data();
|
||||
let web_root = tmp_web_root();
|
||||
let _env = EnvVarGuard::set("IDEA_WEB_ROOT", &web_root);
|
||||
let resource_dir = tmp_resource_dir_with_web_root();
|
||||
let reserved = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
|
||||
let port = reserved.local_addr().unwrap().port();
|
||||
let controller = EmbeddedServerController::new(app_data.clone());
|
||||
let controller =
|
||||
EmbeddedServerController::with_resource_dir(app_data.clone(), Some(resource_dir));
|
||||
controller
|
||||
.save_settings(ServerExposureSettingsDto {
|
||||
mode: ServerExposureMode::LocalOnly,
|
||||
|
||||
@ -255,6 +255,10 @@ pub fn run() {
|
||||
commands::save_profile,
|
||||
commands::save_opencode_provider_profile,
|
||||
commands::list_opencode_providers,
|
||||
commands::save_codex_provider_profile,
|
||||
commands::list_codex_providers,
|
||||
commands::save_claude_provider_profile,
|
||||
commands::list_claude_providers,
|
||||
commands::clone_opencode_profile_from_seed,
|
||||
commands::delete_profile,
|
||||
commands::configure_profiles,
|
||||
|
||||
Reference in New Issue
Block a user