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:
@ -34,12 +34,15 @@ use crate::background_task::{
|
||||
BackgroundTask, BackgroundTaskKind, BackgroundTaskResult, BackgroundTaskWakePolicy,
|
||||
};
|
||||
use crate::events::DomainEvent;
|
||||
use crate::ids::{AgentId, NodeId, ProjectId, ScheduleId, SessionId, SprintId, TaskId};
|
||||
use crate::ids::{
|
||||
AgentId, LocalModelServerId, NodeId, ProjectId, ScheduleId, SessionId, SprintId, TaskId,
|
||||
};
|
||||
use crate::issue::{
|
||||
Issue, IssueCarnet, IssueIndexEntry, IssueListFilter, IssueNumber, IssueRef, IssueVersion,
|
||||
};
|
||||
use crate::markdown::MarkdownDoc;
|
||||
use crate::memory::{Memory, MemoryIndexEntry, MemoryLink, MemorySlug};
|
||||
use crate::model_server::{LocalModelServerConfig, ModelServerEndpoint, ModelServerStatus};
|
||||
use crate::permission::ProjectPermissions;
|
||||
use crate::profile::{AgentProfile, EmbedderProfile};
|
||||
use crate::project::{Project, ProjectPath};
|
||||
@ -206,6 +209,27 @@ pub struct Output {
|
||||
pub stderr: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Opaque handle for a managed local process.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ManagedProcessHandle {
|
||||
/// Stable local process identity as assigned by the adapter.
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
/// Observed status of a managed local process.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ProcessStatus {
|
||||
/// Process is still running.
|
||||
Running,
|
||||
/// Process has exited.
|
||||
Exited {
|
||||
/// Exit code, when available.
|
||||
code: Option<i32>,
|
||||
},
|
||||
/// Handle is unknown to the adapter.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// A location-neutral path used by [`FileSystem`] (local, SFTP, or WSL).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RemotePath(pub String);
|
||||
@ -502,6 +526,41 @@ pub enum ProcessError {
|
||||
Io(String),
|
||||
}
|
||||
|
||||
/// Errors from local model-server ports.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum ModelServerError {
|
||||
/// The referenced configuration does not exist.
|
||||
#[error("not configured")]
|
||||
NotConfigured,
|
||||
/// Server or configuration is invalid.
|
||||
#[error("invalid: {0}")]
|
||||
Invalid(String),
|
||||
/// Filesystem permission denied.
|
||||
#[error("permission denied: {0}")]
|
||||
PermissionDenied(String),
|
||||
/// Required path is not accessible.
|
||||
#[error("path not accessible: {0}")]
|
||||
PathNotAccessible(String),
|
||||
/// The configured port is already in use by another managed server.
|
||||
#[error("port occupied: {0}")]
|
||||
PortOccupied(u16),
|
||||
/// The server is still referenced by another configuration object.
|
||||
#[error("model server in use: {0}")]
|
||||
InUse(String),
|
||||
/// Readiness probe failed unexpectedly.
|
||||
#[error("probe failed: {0}")]
|
||||
Probe(String),
|
||||
/// Process operation failed.
|
||||
#[error("process failed: {0}")]
|
||||
Process(String),
|
||||
/// Store operation failed.
|
||||
#[error("store failed: {0}")]
|
||||
Store(String),
|
||||
/// Readiness timed out.
|
||||
#[error("readiness timed out")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Errors from [`FileSystem`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum FsError {
|
||||
@ -914,6 +973,88 @@ pub trait ProcessSpawner: Send + Sync {
|
||||
async fn run(&self, spec: SpawnSpec) -> Result<Output, ProcessError>;
|
||||
}
|
||||
|
||||
/// Probe readiness of an OpenAI-compatible model server.
|
||||
#[async_trait]
|
||||
pub trait ModelServerProbe: Send + Sync {
|
||||
/// Probes `endpoint` and returns the observed status.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on unexpected probe failure. An unreachable server
|
||||
/// should normally return [`ModelServerStatus::Unreachable`].
|
||||
async fn probe(
|
||||
&self,
|
||||
endpoint: &ModelServerEndpoint,
|
||||
) -> Result<ModelServerStatus, ModelServerError>;
|
||||
}
|
||||
|
||||
/// Manages local long-lived child processes.
|
||||
#[async_trait]
|
||||
pub trait ManagedProcess: Send + Sync {
|
||||
/// Spawns a process from an argv-structured spec.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on spawn failure.
|
||||
async fn spawn(&self, spec: SpawnSpec) -> Result<ManagedProcessHandle, ModelServerError>;
|
||||
|
||||
/// Kills a process.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on kill failure.
|
||||
async fn kill(&self, handle: &ManagedProcessHandle) -> Result<(), ModelServerError>;
|
||||
|
||||
/// Returns the status of a process.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on status failure.
|
||||
async fn status(
|
||||
&self,
|
||||
handle: &ManagedProcessHandle,
|
||||
) -> Result<ProcessStatus, ModelServerError>;
|
||||
}
|
||||
|
||||
/// Builds the argv-structured spawn spec for a local model server.
|
||||
pub trait ModelServerRuntime: Send + Sync {
|
||||
/// Builds a spawn spec from a validated local-server config.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] if the config cannot be launched.
|
||||
fn build_spawn_spec(
|
||||
&self,
|
||||
config: &LocalModelServerConfig,
|
||||
) -> Result<SpawnSpec, ModelServerError>;
|
||||
}
|
||||
|
||||
/// Persists local model-server configuration in the global IdeA store.
|
||||
#[async_trait]
|
||||
pub trait ModelServerRegistry: Send + Sync {
|
||||
/// Returns one server config by id.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on store failure.
|
||||
async fn get(
|
||||
&self,
|
||||
id: &LocalModelServerId,
|
||||
) -> Result<Option<LocalModelServerConfig>, ModelServerError>;
|
||||
|
||||
/// Lists all server configs.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on store failure.
|
||||
async fn list(&self) -> Result<Vec<LocalModelServerConfig>, ModelServerError>;
|
||||
|
||||
/// Saves one server config.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on store failure.
|
||||
async fn save(&self, config: LocalModelServerConfig) -> Result<(), ModelServerError>;
|
||||
|
||||
/// Deletes one server config.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`ModelServerError`] on store failure.
|
||||
async fn delete(&self, id: LocalModelServerId) -> Result<(), ModelServerError>;
|
||||
}
|
||||
|
||||
/// Location-neutral filesystem access.
|
||||
#[async_trait]
|
||||
pub trait FileSystem: Send + Sync {
|
||||
|
||||
Reference in New Issue
Block a user