feat(model-server): backend modèles locaux — serveur llama.cpp intégré (#35) et profils OpenCode locaux multiples (#36)

Sprint « Modeles locaux », couche backend (domain/application/infra/app-tauri).

#35 — Serveur de modèle local intégré (llama.cpp) :
- domain: model_server.rs (agrégat + statut), ports ModelServerProbe /
  ManagedProcess / ModelServerRuntime / ModelServerRegistry, événements
  model_server_status_changed et agent_launch_failed.
- application: use case EnsureLocalModelServer branché sur LaunchAgent.
- infrastructure: adapters HttpOpenAiCompatibleProbe, LlamaCppRuntime,
  LocalManagedProcess, FsModelServerRegistry.
- app-tauri: DTO plat LocalModelServerConfigDto, commandes
  list/save/delete_model_server avec garde model_server_in_use, wiring.

#36 — Profils OpenCode locaux multiples :
- domain: VO LocalModelServerId, OpenCodeConfig.local_model_server_id.
- application: use case CloneOpenCodeProfileFromSeed.
- app-tauri: commande clone_opencode_profile_from_seed, DTO/wiring.

Tests verts (exécution réelle) : domain+application OK, app-tauri
dto_model_servers 3/3 et dto_profiles 10/10, infra model_server 2/2,
application model_server+profile_usecases 19/19, cargo build workspace Finished.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 15:50:08 +02:00
parent 2e98f1fbb9
commit b82ac76f8b
24 changed files with 3147 additions and 94 deletions

View File

@ -34,28 +34,30 @@ use crate::dto::{
parse_task_id, parse_template_id, parse_ticket_id, AgentDriftListDto, AgentDto, AgentListDto,
AssignSkillRequestDto, AttachLiveAgentRequestDto, AttachLiveAgentResponseDto,
BackgroundTaskDto, ChangeAgentProfileDto, ChangeAgentProfileRequestDto,
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, OpenTerminalRequestDto, ProfileDto,
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, OpenTerminalRequestDto, ProfileDto,
ProfileListDto, ProjectDto, ProjectListDto, ProjectPermissionsDto, ProjectWorkStateDto,
ReadAgentContextResponseDto, ReadConversationPageRequestDto, ReattachChatDto,
ReattachResultDto, RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk,
ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto, ResumableAgentListDto,
SaveEmbedderProfileRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto,
SetActiveLayoutResultDto, SkillDto, SkillListDto, StopLiveAgentRequestDto,
StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
TemplateListDto, TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
UpdateAgentContextRequestDto, UpdateAgentPermissionsRequestDto, UpdateMemoryRequestDto,
UpdateProjectContextRequestDto, UpdateProjectPermissionsRequestDto, UpdateSkillRequestDto,
UpdateTemplateRequestDto, WriteTerminalRequestDto,
SaveEmbedderProfileRequestDto, SaveModelServerRequestDto, SaveProfileRequestDto,
SetActiveLayoutRequestDto, SetActiveLayoutResultDto, SkillDto, SkillListDto,
StopLiveAgentRequestDto, StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto,
SyncResultDto, TemplateDto, TemplateListDto, TerminalClosedDto, TerminalSessionDto,
TurnPageDto, UnassignSkillRequestDto, UpdateAgentContextRequestDto,
UpdateAgentPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
UpdateProjectPermissionsRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
WriteTerminalRequestDto, parse_model_server_id, save_model_server_input,
};
use crate::pty::{PtyBridge, PtyChunk};
use crate::state::AppState;
@ -793,6 +795,25 @@ pub async fn save_profile(
.map_err(ErrorDto::from)
}
/// `clone_opencode_profile_from_seed` — create a new OpenCode profile instance
/// from the canonical `opencode-llamacpp` seed/template.
///
/// # Errors
/// Returns an [`ErrorDto`] (`STORE` on profiles I/O failure, `INVALID` for a
/// blank requested name).
#[tauri::command]
pub async fn clone_opencode_profile_from_seed(
request: CloneOpenCodeProfileFromSeedRequestDto,
state: State<'_, AppState>,
) -> Result<ProfileDto, ErrorDto> {
state
.clone_opencode_profile_from_seed
.execute(request.into())
.await
.map(ProfileDto::from)
.map_err(ErrorDto::from)
}
/// `delete_profile` — delete a profile by id.
///
/// # Errors
@ -836,6 +857,73 @@ pub async fn configure_profiles(
.map_err(ErrorDto::from)
}
/// `list_model_servers` — list configured local model servers.
///
/// # Errors
/// Returns an [`ErrorDto`] on registry failure.
#[tauri::command]
pub async fn list_model_servers(
state: State<'_, AppState>,
) -> Result<ModelServerConfigListDto, ErrorDto> {
state
.list_model_servers
.execute()
.await
.map(ModelServerConfigListDto::from)
.map_err(ErrorDto::from)
}
/// `save_model_server` — upsert a local model server config.
///
/// # Errors
/// Returns an [`ErrorDto`] on registry failure.
#[tauri::command]
pub async fn save_model_server(
request: SaveModelServerRequestDto,
state: State<'_, AppState>,
) -> Result<ModelServerConfigDto, ErrorDto> {
let server_id = parse_model_server_id(&request.config.id)?;
let existing = state
.list_model_servers
.execute()
.await
.map_err(ErrorDto::from)?
.servers
.into_iter()
.find(|config| config.id == server_id);
let input = save_model_server_input(request, existing.as_ref())?;
state
.save_model_server
.execute(input)
.await
.map(ModelServerConfigDto::from)
.map_err(ErrorDto::from)
}
/// `delete_model_server` — delete a local model server config when unused.
///
/// # Errors
/// Returns `model_server_in_use` if any OpenCode profile still references it.
#[tauri::command]
pub async fn delete_model_server(
server_id: String,
state: State<'_, AppState>,
) -> Result<(), ErrorDto> {
let server_id = parse_model_server_id(&server_id)?;
state
.delete_model_server
.execute(application::DeleteModelServerInput { server_id })
.await
.map_err(model_server_command_error)
}
fn model_server_command_error(err: AppError) -> ErrorDto {
match err {
AppError::ModelServer { code, message } => ErrorDto { code, message },
other => ErrorDto::from(other),
}
}
// ---------------------------------------------------------------------------
// Embedder profiles & engines (LOT C2 — §14.5.3)
// ---------------------------------------------------------------------------