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:
@ -7,7 +7,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::DomainError;
|
||||
use crate::ids::ProfileId;
|
||||
use crate::ids::{LocalModelServerId, ProfileId};
|
||||
use crate::permission::ProjectorKey;
|
||||
|
||||
/// Strategy for injecting an agent's `.md` context into the launched CLI.
|
||||
@ -295,6 +295,11 @@ pub struct OpenCodeConfig {
|
||||
/// Active les attachments côté modèle OpenCode. Défaut effectif : `false`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub attachment: Option<bool>,
|
||||
/// Serveur local managé associé, quand ce profil dépend d'un lifecycle IdeA.
|
||||
///
|
||||
/// `None` garde le comportement existant : profil OpenCode manuel/externe.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub local_model_server_id: Option<LocalModelServerId>,
|
||||
}
|
||||
|
||||
impl OpenCodeConfig {
|
||||
@ -325,9 +330,17 @@ impl OpenCodeConfig {
|
||||
model,
|
||||
reasoning,
|
||||
attachment,
|
||||
local_model_server_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Builder : associe cette configuration à un serveur local managé.
|
||||
#[must_use]
|
||||
pub const fn with_local_model_server_id(mut self, id: LocalModelServerId) -> Self {
|
||||
self.local_model_server_id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Valeur effective de `reasoning` quand le profil ne la porte pas.
|
||||
#[must_use]
|
||||
pub fn reasoning_enabled(&self) -> bool {
|
||||
@ -1107,7 +1120,7 @@ impl AgentProfile {
|
||||
#[cfg(test)]
|
||||
mod mcp_tests {
|
||||
use super::*;
|
||||
use crate::ids::ProfileId;
|
||||
use crate::ids::{LocalModelServerId, ProfileId};
|
||||
|
||||
/// A reference profile without any MCP capability (the historical shape).
|
||||
fn profile_without_mcp() -> AgentProfile {
|
||||
@ -1225,6 +1238,46 @@ mod mcp_tests {
|
||||
assert!(OpenCodeConfig::new("http://localhost:8080/v1", None, " ", None, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opencode_config_omits_local_model_server_id_when_absent() {
|
||||
let config = OpenCodeConfig::new(
|
||||
"http://localhost:8080/v1",
|
||||
None,
|
||||
"qwen3-coder-30b",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let json = serde_json::to_string(&config).expect("serialise");
|
||||
assert!(
|
||||
!json.contains("localModelServerId"),
|
||||
"manual external OpenCode profiles must keep the legacy JSON shape: {json}"
|
||||
);
|
||||
let back: OpenCodeConfig = serde_json::from_str(&json).expect("deserialise");
|
||||
assert_eq!(back.local_model_server_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opencode_config_serialises_local_model_server_id_camelcase() {
|
||||
let server_id = LocalModelServerId::from_uuid(uuid::Uuid::from_u128(35));
|
||||
let config = OpenCodeConfig::new(
|
||||
"http://localhost:8080/v1",
|
||||
None,
|
||||
"qwen3-coder-30b",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.with_local_model_server_id(server_id);
|
||||
|
||||
let value = serde_json::to_value(&config).expect("serialise");
|
||||
assert_eq!(value["localModelServerId"], server_id.to_string());
|
||||
assert!(value.get("local_model_server_id").is_none());
|
||||
let back: OpenCodeConfig = serde_json::from_value(value).expect("deserialise");
|
||||
assert_eq!(back.local_model_server_id, Some(server_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_compatible_stays_non_mcp_native_even_with_mcp_declared() {
|
||||
let profile = profile_without_mcp()
|
||||
|
||||
Reference in New Issue
Block a user