feat: finalise multi-profil Codex/Claude avec catalogue de modèles
- Backend : clone_profile_from_seed généralisé (non OpenCode) - Backend : catalogue static Claude/Codex (3 modèles chacun, 1 recommandé) - Backend : commandes Tauri list_claude_models/list_codex_models - Frontend : ProfilesSettings refonte en onglets Codex/Claude + create/duplicate/edit/delete - Frontend : ModelSelect searchable partagé + fallback saisie manuelle - Frontend : assignation agent nom · modèle - Tests QA : 4 profils modèles distincts (2 Claude, 2 Codex) assignés à agents
This commit is contained in:
@ -41,22 +41,23 @@ use crate::dto::{
|
||||
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,
|
||||
CloneProfileFromSeedRequestDto, 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,
|
||||
ProfileModelCatalogDto, ProjectDto, ProjectListDto, ProjectMcpToolPermissionsDto,
|
||||
ProjectPermissionsDto, ProjectSystemPermissionsDto, ProjectWorkStateDto,
|
||||
ReadAgentContextResponseDto, ReadConversationPageRequestDto, ReattachChatDto,
|
||||
ReattachResultDto, RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk,
|
||||
ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
ResumableAgentListDto, SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
@ -1188,6 +1189,22 @@ pub async fn list_opencode_providers(
|
||||
Ok(state.list_opencode_providers.execute().into())
|
||||
}
|
||||
|
||||
/// `list_claude_models` — static curated Claude model catalogue.
|
||||
#[tauri::command]
|
||||
pub async fn list_claude_models(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProfileModelCatalogDto, ErrorDto> {
|
||||
Ok(state.list_claude_models.execute().into())
|
||||
}
|
||||
|
||||
/// `list_codex_models` — static curated Codex model catalogue.
|
||||
#[tauri::command]
|
||||
pub async fn list_codex_models(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProfileModelCatalogDto, ErrorDto> {
|
||||
Ok(state.list_codex_models.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`.
|
||||
@ -1227,6 +1244,25 @@ pub async fn clone_opencode_profile_from_seed(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `clone_profile_from_seed` — create a new profile instance from a
|
||||
/// persisted/reference seed, with optional name/model overrides.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`NOT_FOUND` for an unknown seed, `STORE` on profiles
|
||||
/// I/O failure, `INVALID` for a blank requested name/model).
|
||||
#[tauri::command]
|
||||
pub async fn clone_profile_from_seed(
|
||||
request: CloneProfileFromSeedRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProfileDto, ErrorDto> {
|
||||
state
|
||||
.clone_profile_from_seed
|
||||
.execute(request.into())
|
||||
.await
|
||||
.map(ProfileDto::from)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `delete_profile` — delete a profile by id.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@ -255,6 +255,9 @@ pub fn run() {
|
||||
commands::save_profile,
|
||||
commands::save_opencode_provider_profile,
|
||||
commands::list_opencode_providers,
|
||||
commands::list_claude_models,
|
||||
commands::list_codex_models,
|
||||
commands::clone_profile_from_seed,
|
||||
commands::clone_opencode_profile_from_seed,
|
||||
commands::delete_profile,
|
||||
commands::configure_profiles,
|
||||
|
||||
@ -4,15 +4,17 @@
|
||||
|
||||
use app_tauri_lib::dto::{
|
||||
parse_delete_profile, parse_profile_id, CloneOpenCodeProfileFromSeedRequestDto,
|
||||
ConfigureProfilesRequestDto, DetectProfilesRequestDto, DetectProfilesResponseDto,
|
||||
FirstRunStateDto, ProfileListDto, SaveProfileRequestDto,
|
||||
CloneProfileFromSeedRequestDto, ConfigureProfilesRequestDto, DetectProfilesRequestDto,
|
||||
DetectProfilesResponseDto, FirstRunStateDto, ProfileListDto, ProfileModelCatalogDto,
|
||||
SaveProfileRequestDto,
|
||||
};
|
||||
use application::{
|
||||
CloneOpenCodeProfileFromSeedInput, ConfigureProfilesInput, DetectProfilesInput,
|
||||
DetectProfilesOutput, FirstRunStateOutput, ProfileAvailability, SaveProfileInput,
|
||||
CloneOpenCodeProfileFromSeedInput, CloneProfileFromSeedInput, ConfigureProfilesInput,
|
||||
DetectProfilesInput, DetectProfilesOutput, FirstRunStateOutput, ProfileAvailability,
|
||||
SaveProfileInput,
|
||||
};
|
||||
use domain::ids::{LocalModelServerId, ProfileId};
|
||||
use domain::profile::{AgentProfile, ContextInjection, OpenCodeConfig};
|
||||
use domain::profile::{AgentProfile, ContextInjection, OpenCodeConfig, StructuredAdapter};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -121,6 +123,41 @@ fn clone_opencode_profile_from_seed_request_deserialises_camelcase_config() {
|
||||
assert_eq!(opencode.local_model_server_id, Some(server_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_profile_from_seed_request_deserialises_camelcase_overrides() {
|
||||
let seed_id = Uuid::from_u128(42);
|
||||
let raw = json!({
|
||||
"seedProfileId": seed_id.to_string(),
|
||||
"name": "Codex GPT-5",
|
||||
"model": "gpt-5-codex"
|
||||
});
|
||||
|
||||
let dto: CloneProfileFromSeedRequestDto = serde_json::from_value(raw).unwrap();
|
||||
let input: CloneProfileFromSeedInput = dto.into();
|
||||
assert_eq!(input.seed_profile_id, ProfileId::from_uuid(seed_id));
|
||||
assert_eq!(input.name.as_deref(), Some("Codex GPT-5"));
|
||||
assert_eq!(input.model.as_deref(), Some("gpt-5-codex"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_model_catalogue_dto_serialises_searchable_camelcase_entries() {
|
||||
let dto = ProfileModelCatalogDto(vec![app_tauri_lib::dto::ProfileModelCatalogEntryDto {
|
||||
adapter: StructuredAdapter::Codex,
|
||||
model_id: "gpt-5-codex".to_owned(),
|
||||
display_name: "GPT-5 Codex".to_owned(),
|
||||
aliases: vec!["codex".to_owned()],
|
||||
recommended: true,
|
||||
}]);
|
||||
|
||||
let value = serde_json::to_value(&dto).unwrap();
|
||||
let arr = value.as_array().expect("transparent array");
|
||||
assert_eq!(arr[0]["adapter"], "codex");
|
||||
assert_eq!(arr[0]["modelId"], "gpt-5-codex");
|
||||
assert_eq!(arr[0]["displayName"], "GPT-5 Codex");
|
||||
assert_eq!(arr[0]["aliases"], json!(["codex"]));
|
||||
assert_eq!(arr[0]["recommended"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opencode_config_dto_omits_local_model_server_id_when_none() {
|
||||
let config = OpenCodeConfig::new(
|
||||
|
||||
Reference in New Issue
Block a user