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:
111
crates/app-tauri/tests/dto_model_servers.rs
Normal file
111
crates/app-tauri/tests/dto_model_servers.rs
Normal file
@ -0,0 +1,111 @@
|
||||
//! DTO contract tests for local model-server IPC.
|
||||
|
||||
use app_tauri_lib::dto::{
|
||||
ModelServerConfigDto, ModelServerKindDto, SaveModelServerRequestDto, StopPolicyDto,
|
||||
save_model_server_input,
|
||||
};
|
||||
use domain::model_server::{
|
||||
ExecutablePath, LocalModelRef, LocalModelServerConfig, LocalModelServerKind, ModelPath,
|
||||
ModelServerEndpoint,
|
||||
};
|
||||
use domain::{LocalModelServerId, StopPolicy};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn sid(n: u128) -> LocalModelServerId {
|
||||
LocalModelServerId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
fn domain_config(id: LocalModelServerId, model_id: &str) -> LocalModelServerConfig {
|
||||
LocalModelServerConfig::new(
|
||||
id,
|
||||
LocalModelServerKind::LlamaCpp,
|
||||
"Local Qwen",
|
||||
ModelServerEndpoint::new("http://localhost:8080", 8080).unwrap(),
|
||||
LocalModelRef::new(
|
||||
model_id,
|
||||
"Qwen",
|
||||
Some(ModelPath::new("/models/qwen.gguf").unwrap()),
|
||||
"qwen3-coder",
|
||||
)
|
||||
.unwrap(),
|
||||
Some(ExecutablePath::new("/usr/bin/llama-server").unwrap()),
|
||||
vec!["--ctx-size".to_owned(), "4096".to_owned()],
|
||||
true,
|
||||
StopPolicy::StopOnAppExit,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_server_dto_serialises_flat_camelcase_wire_shape() {
|
||||
let dto = ModelServerConfigDto::from_domain(domain_config(sid(35), "internal-model-id"));
|
||||
|
||||
let value = serde_json::to_value(dto).unwrap();
|
||||
|
||||
assert_eq!(value["id"], sid(35).to_string());
|
||||
assert_eq!(value["kind"], "llamaCpp");
|
||||
assert_eq!(value["baseURL"], "http://localhost:8080/v1");
|
||||
assert_eq!(value["modelPath"], "/models/qwen.gguf");
|
||||
assert_eq!(value["servedModelName"], "qwen3-coder");
|
||||
assert_eq!(value["binaryPath"], "/usr/bin/llama-server");
|
||||
assert_eq!(value["stopPolicy"], "stopOnAppExit");
|
||||
assert!(value.get("model").is_none(), "domain model must not leak");
|
||||
assert!(value.get("endpoint").is_none(), "domain endpoint must not leak");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_server_dto_deserialises_flat_wire_shape_and_generates_model_id() {
|
||||
let raw = json!({
|
||||
"id": sid(36).to_string(),
|
||||
"kind": "llamaCpp",
|
||||
"name": "Local Qwen",
|
||||
"baseURL": "http://localhost:8081",
|
||||
"port": 8081,
|
||||
"modelPath": "/models/qwen.gguf",
|
||||
"servedModelName": "qwen3-coder",
|
||||
"binaryPath": "/usr/bin/llama-server",
|
||||
"args": ["--ctx-size", "4096"],
|
||||
"autoStart": true,
|
||||
"stopPolicy": "stopOnAppExit"
|
||||
});
|
||||
|
||||
let request = SaveModelServerRequestDto {
|
||||
config: serde_json::from_value(raw).unwrap(),
|
||||
};
|
||||
let input = save_model_server_input(request, None).unwrap();
|
||||
|
||||
assert_eq!(input.config.id, sid(36));
|
||||
assert_eq!(input.config.kind, LocalModelServerKind::LlamaCpp);
|
||||
assert_eq!(input.config.endpoint.base_url, "http://localhost:8081/v1");
|
||||
assert_eq!(
|
||||
input.config.model.path.unwrap().as_str(),
|
||||
"/models/qwen.gguf"
|
||||
);
|
||||
assert_eq!(input.config.model.served_name, "qwen3-coder");
|
||||
assert_eq!(input.config.model.label, "qwen3-coder");
|
||||
assert!(!input.config.model.id.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_server_dto_preserves_existing_internal_model_id_on_upsert() {
|
||||
let existing = domain_config(sid(37), "stable-internal-id");
|
||||
let dto = ModelServerConfigDto {
|
||||
id: sid(37).to_string(),
|
||||
kind: ModelServerKindDto::LlamaCpp,
|
||||
name: "Updated Qwen".to_owned(),
|
||||
base_url: "http://localhost:8082/v1".to_owned(),
|
||||
port: 8082,
|
||||
model_path: Some("/models/qwen-updated.gguf".to_owned()),
|
||||
served_model_name: "qwen3-coder-updated".to_owned(),
|
||||
binary_path: Some("/usr/bin/llama-server".to_owned()),
|
||||
args: Vec::new(),
|
||||
auto_start: true,
|
||||
stop_policy: StopPolicyDto::StopOnAppExit,
|
||||
};
|
||||
|
||||
let config = dto.into_domain(Some(&existing)).unwrap();
|
||||
|
||||
assert_eq!(config.model.id, "stable-internal-id");
|
||||
assert_eq!(config.model.served_name, "qwen3-coder-updated");
|
||||
}
|
||||
Reference in New Issue
Block a user