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/infrastructure/tests/model_server.rs
Normal file
111
crates/infrastructure/tests/model_server.rs
Normal file
@ -0,0 +1,111 @@
|
||||
//! Infrastructure tests for local model-server adapters.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::model_server::{
|
||||
ExecutablePath, LocalModelRef, LocalModelServerConfig, LocalModelServerKind, ModelPath,
|
||||
ModelServerEndpoint, StopPolicy,
|
||||
};
|
||||
use domain::ports::{FileSystem, ModelServerRegistry, ModelServerRuntime, RemotePath};
|
||||
use domain::LocalModelServerId;
|
||||
use infrastructure::{FsModelServerRegistry, LlamaCppRuntime, LocalFileSystem};
|
||||
use uuid::Uuid;
|
||||
|
||||
struct TempDir(PathBuf);
|
||||
|
||||
impl TempDir {
|
||||
fn new() -> Self {
|
||||
let path = std::env::temp_dir().join(format!("idea-model-server-{}", Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&path).unwrap();
|
||||
Self(path)
|
||||
}
|
||||
|
||||
fn app_data_dir(&self) -> String {
|
||||
self.0.to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
fn child(&self, name: &str) -> RemotePath {
|
||||
RemotePath::new(self.0.join(name).to_string_lossy().into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn sid(n: u128) -> LocalModelServerId {
|
||||
LocalModelServerId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
fn config(id: LocalModelServerId, port: u16) -> LocalModelServerConfig {
|
||||
let binary = std::env::current_exe()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
LocalModelServerConfig::new(
|
||||
id,
|
||||
LocalModelServerKind::LlamaCpp,
|
||||
"Local Qwen",
|
||||
ModelServerEndpoint::new(format!("http://localhost:{port}/v1"), port).unwrap(),
|
||||
LocalModelRef::new(
|
||||
"qwen",
|
||||
"Qwen",
|
||||
Some(ModelPath::new("/models/qwen.gguf").unwrap()),
|
||||
"qwen3-coder-30b",
|
||||
)
|
||||
.unwrap(),
|
||||
Some(ExecutablePath::new(binary).unwrap()),
|
||||
vec!["--ctx-size".to_owned(), "8192".to_owned()],
|
||||
true,
|
||||
StopPolicy::StopOnAppExit,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn llamacpp_runtime_builds_structured_argv() {
|
||||
let spec = LlamaCppRuntime::new()
|
||||
.build_spawn_spec(&config(sid(1), 8080))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
spec.command,
|
||||
std::env::current_exe().unwrap().to_string_lossy()
|
||||
);
|
||||
assert_eq!(
|
||||
spec.args,
|
||||
vec![
|
||||
"--model",
|
||||
"/models/qwen.gguf",
|
||||
"--port",
|
||||
"8080",
|
||||
"--ctx-size",
|
||||
"8192"
|
||||
]
|
||||
);
|
||||
assert!(spec.context_plan.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fs_model_server_registry_roundtrips_global_json() {
|
||||
let tmp = TempDir::new();
|
||||
let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
|
||||
let registry = FsModelServerRegistry::new(Arc::clone(&fs), tmp.app_data_dir());
|
||||
|
||||
let first = config(sid(2), 8081);
|
||||
registry.save(first.clone()).await.unwrap();
|
||||
|
||||
assert_eq!(registry.get(&first.id).await.unwrap(), Some(first.clone()));
|
||||
assert_eq!(registry.list().await.unwrap(), vec![first.clone()]);
|
||||
assert!(
|
||||
fs.exists(&tmp.child("model-servers.json")).await.unwrap(),
|
||||
"registry must live in global app-data dir"
|
||||
);
|
||||
|
||||
let updated = config(sid(2), 8082);
|
||||
registry.save(updated.clone()).await.unwrap();
|
||||
assert_eq!(registry.list().await.unwrap(), vec![updated]);
|
||||
}
|
||||
Reference in New Issue
Block a user