feat(model-server): frontend modèles locaux — badge de statut & CRUD serveurs (#35) et wizard multi-profils OpenCode (#36)

Sprint « Modeles locaux », couche frontend.

#35 :
- F35.1 badge de statut de lancement du serveur local
  (ModelServerLaunchBadge + useAgentsModelServer).
- F35.2 feature model-servers : CRUD (ModelServersPanel / useModelServers /
  gateway modelServer) et ModelServerSelect.

#36 :
- Liste multi-profils OpenCode dans le wizard de premier lancement,
  gateway de clonage (clone_opencode_profile_from_seed).

Tests verts (exécution réelle) : tsc propre, vitest 608/608.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 15:50:18 +02:00
parent b82ac76f8b
commit 72476a650a
23 changed files with 1893 additions and 15 deletions

View File

@ -13,11 +13,92 @@ export interface HealthReport {
note: string | null;
}
/**
* Lifecycle status of a local model server during an agent launch (F35, mirror
* of the backend `ModelServerStatusDto`, tagged on `state`, camelCase wire).
*
* Contract note — the backend event carries the `serverId` on the *envelope*
* ({@link DomainEvent} `modelServerStatusChanged`), not inside the status, and it
* does NOT emit `baseURL`/`model` in the status payload: the UI reads what the
* backend actually sends. `reused` (on `ready`) tells apart a freshly-started
* server from a reused running one.
*/
export type ModelServerStatus =
| { state: "notConfigured" }
| { state: "probing" }
| { state: "starting" }
| { state: "ready"; reused: boolean }
| { state: "failed"; code: string; message: string };
/**
* Stop policy of a managed local model server (mirror of the backend
* `StopPolicyDto`, camelCase wire):
* - `keepAlive`: leave the process running,
* - `stopOnAppExit`: stop when IdeA exits (the effective default),
* - `stopWhenUnused`: stop when no profile references it.
*/
export type StopPolicy = "keepAlive" | "stopOnAppExit" | "stopWhenUnused";
/**
* A declared local model server (F35, mirror of the backend flat
* `LocalModelServerConfigDto`, camelCase wire). Global to IdeA (not project
* scoped). Referenced by an OpenCode profile through
* `OpenCodeConfig.localModelServerId` (= this `id`).
*
* Contract notes:
* - `id` is a UUID **minted client-side** on create (the backend requires a valid
* UUID; it does not generate the server id, only an internal model id it hides).
* - `servedModelName` is what the user configures and what is sent to OpenCode as
* the `model` — the backend's internal `model.id`/`model.label` are not exposed.
* - `modelPath` is required by the backend when `autoStart` is `true`.
*/
export interface LocalModelServerConfig {
id: string;
/** Only `llamaCpp` is supported today (camelCase, not "llamacpp"). */
kind: "llamaCpp";
name: string;
/** OpenAI-compatible base URL served by `llama-server` (normalised to `/v1`). */
baseURL: string;
port: number;
/** Absolute `.gguf` path. Required when `autoStart` is `true`. */
modelPath?: string;
/** Model name exposed by the server and sent to OpenCode as `model`. */
servedModelName: string;
/** Explicit `llama-server` executable path/command (optional). */
binaryPath?: string;
args: string[];
autoStart: boolean;
stopPolicy: StopPolicy;
}
/** A domain event relayed from the backend (tagged union on `type`). */
export type DomainEvent =
| { type: "projectCreated"; projectId: string }
| { type: "agentLaunched"; agentId: string; sessionId: string }
| { type: "agentExited"; agentId: string; code: number }
| {
/**
* A local model server changed lifecycle state while (re)launching an
* agent whose OpenCode profile binds `localModelServerId` (F35). Keyed by
* `serverId`; the frontend correlates it back to the agent through the
* profile's `opencode.localModelServerId`.
*/
type: "modelServerStatusChanged";
serverId: string;
status: ModelServerStatus;
}
| {
/**
* An agent launch failed before a runtime session was created (F35). Unlike
* a generic "agent failed", this carries an actionable `message` and a
* stable `code`; `cause` namespaces the failure (e.g. `"model_server"`).
*/
type: "agentLaunchFailed";
agentId: string;
cause: string;
code: string;
message: string;
}
| { type: "agentProfileChanged"; agentId: string; profileId: string }
| { type: "agentBusyChanged"; agentId: string; busy: boolean }
| {
@ -687,6 +768,13 @@ export interface OpenCodeConfig {
reasoning?: boolean;
/** Enables attachments. Effective default: `false`. */
attachment?: boolean;
/**
* Optional id of a managed local model server (F35) this profile binds to.
* When set, IdeA can start/stop the referenced `llama-server` for the profile;
* omitted when the profile points at an externally-managed endpoint (mirrors
* the backend `skip_serializing_if`). Opaque string id on the wire.
*/
localModelServerId?: string;
}
/**