From 173170b68ecb28bce75da2d43e3b3447c10b831b Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 29 Oct 2026 01:47:34 +0100 Subject: [PATCH] Add models list functionality and update related files --- README.md | 10 +++++-- SPECS_TECH.md | 4 ++- packages/agent/src/commands.ts | 2 +- packages/agent/src/index.ts | 1 + packages/agent/src/init.ts | 20 +++++++++---- packages/agent/src/tools/listmodels.test.ts | 26 +++++++++++++++++ packages/agent/src/tools/listmodels.ts | 32 +++++++++++++++++++++ packages/cli/src/index.ts | 24 ++++++++++++++-- packages/cli/src/models-list.test.ts | 18 ++++++++++++ packages/cli/src/models-list.ts | 8 ++++++ packages/cli/src/subagent-child.ts | 3 +- packages/cli/src/workflow-run.ts | 8 ++++-- 12 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 packages/agent/src/tools/listmodels.test.ts create mode 100644 packages/agent/src/tools/listmodels.ts create mode 100644 packages/cli/src/models-list.test.ts create mode 100644 packages/cli/src/models-list.ts diff --git a/README.md b/README.md index fd5b8db..f79eada 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ tient compte de `MAIA_CWD`, puis de `INIT_CWD` (dossier où pnpm/npm a été lan | `core` | Contrats (`LLMProvider`, `Tool`, `AgentUI`…), boucle agent, sessions, `!`, `@fichier`, `§skill` | | `providers` | Adaptateurs LLM : `litellm` (provider unique : requêtes au format OpenAI vers LiteLLM, voir ci-dessous) | | `ui` | Interface Ink/React (journal, saisie, listes de choix, confirmations, spinner) | -| `agent` | L'agent MAIA : contexte (MAIA.md / CLAUDE.md), skills `.maia/skills` + outils Read/Glob/Grep/Write/Edit/Bash/TodoWrite/WebFetch/CurrentModel | +| `agent` | L'agent MAIA : contexte (MAIA.md / CLAUDE.md), skills `.maia/skills` + outils Read/Glob/Grep/Write/Edit/Bash/TodoWrite/WebFetch/CurrentModel/ListModels | | `cli` | Point d'entrée : configuration, boucle de conversation | Résolution du modèle (pas de défaut : s'il n'est pas défini, il est demandé) : option `--model` > variable `MAIA_MODEL` @@ -59,6 +59,7 @@ Spécifications techniques détaillées (contrats, formats, constantes) : [`SPEC | `/help` | aide (commandes, skills) | | `/permissions` | affiche les règles de permissions chargées, par fichier : badges `ALLOW`, `ASK`, `DENY` suivis des règles | | `/model` | recharge la liste des modèles LiteLLM et change de modèle (conversation conservée) | +| `/models` | affiche la liste des modèles disponibles auprès de LiteLLM (le modèle actif est signalé ●), sans changer de modèle | | `/clear` | supprime le contexte de la session et recharge `MAIA.md` / `CLAUDE.md` et les skills, comme au démarrage (la session garde son nom) | | `/createAgent [nom]` | crée un sous-agent depuis le modèle `.ressources/AGENT.md` : demande le niveau (projet `./.maia/agents` ou global `~/.maia/agents`) puis le nom | | `/agents` | liste les sous-agents chargés (nom, origine, description) | @@ -202,7 +203,7 @@ Au démarrage, le fichier **`~/.maia/settings.json`** (dossier de l'utilisateur, Si le fichier est absent ou incomplet, MAIA s'arrête avec un message qui en donne un exemple. Choix du modèle : au démarrage (sauf si `--model` ou `MAIA_MODEL` le définit), MAIA récupère les modèles auprès de LiteLLM -et demande de choisir celui de la session. `/model` rouvre cette liste en cours de session (la conversation est conservée). +et demande de choisir celui de la session. `/model` rouvre cette liste en cours de session (la conversation est conservée) ; `/models` l'affiche seulement, sans rien changer. Spécification explicite : `litellm:` (ou le nom seul). ## Agent MAIA : contexte et skills @@ -278,10 +279,11 @@ La commande `/permissions` affiche les règles actives, fichier par fichier, ave Exemple pour éviter les confirmations sur les outils en lecture seule : voir `settings.example.json`. -## Outils TodoWrite, WebFetch et CurrentModel +## Outils TodoWrite, WebFetch, CurrentModel et ListModels - **TodoWrite** : liste de tâches de la session (`pending` / `in_progress` / `completed`, forme `activeForm` pour la tâche en cours). Chaque appel remplace la liste ; le résultat (☐ ▶ ☑) est affiché à l'utilisateur et renvoyé au modèle. Un avertissement est ajouté si plusieurs tâches sont « en cours ». - **CurrentModel** : renvoie le modèle LLM utilisé par la session (valeur à jour après un `/model`). Sans paramètre, sans confirmation ; le modèle est lu au moment de l'appel. +- **ListModels** : renvoie la liste des modèles disponibles auprès de LiteLLM (même source que `/models`, interrogée à chaque appel), en signalant celui de la session. Sans paramètre, sans confirmation. Permet à l'agent de répondre à « quels sont les modèles disponibles ? » ; il ne change pas de modèle (c'est `/model`, côté utilisateur). Disponible aussi pour les sous-agents et l'agent parent des workflows. - **WebFetch** : télécharge une URL http(s) (HTML converti en Markdown avec `turndown`, JSON mis en forme, texte brut ; 5 Mo et 30 s maximum, `max_length` caractères renvoyés). Pas de JavaScript exécuté, pas de PDF ni d'images. - Confirmation demandée par défaut ; règle `allow` possible : `WebFetch(domain:docs.example.com)` (voir « Permissions »). - Une redirection vers un **autre domaine** n'est pas suivie : l'outil renvoie la nouvelle URL, que le modèle doit rappeler (ce qui repasse par la confirmation). @@ -572,3 +574,5 @@ Journal des évolutions, dans l'ordre : 56. **Diagnostic des touches** : `MAIA_DEBUG_KEYS=1 pnpm maia` écrit chaque touche reçue (texte + drapeaux Ink) dans `./.maia-keys.log`, pour comprendre pourquoi une touche (Maj+Entrée…) n'est pas reconnue. 57. **Barre de contexte : js-tiktoken uniquement.** L'essai avec `usage.total_tokens` du serveur a été retiré : la taille du contexte est toujours comptée localement avec js-tiktoken, la consommation du serveur ne sert qu'à détecter une réponse coupée. 58. **`/delete`** : suppression d'une ou plusieurs sessions (Espace pour cocher, Suppr pour supprimer, confirmation), sauf la session courante. +59. **`/models`** : liste des modèles disponibles auprès de LiteLLM (interrogé à chaque appel, `LiteLLM_URL` + `LiteLLM_Models`), modèle actif signalé ; aucun changement de modèle. +60. **Outil `ListModels`** : l'agent (et les sous-agents, les workflows) peut lister les modèles disponibles auprès de LiteLLM. diff --git a/SPECS_TECH.md b/SPECS_TECH.md index 5fac00f..f2f5495 100644 --- a/SPECS_TECH.md +++ b/SPECS_TECH.md @@ -154,6 +154,7 @@ Sortie : `/exit`, Ctrl+D (saisie vide), Ctrl+C (saisie), SIGTERM, SIGHUP. La ses | `/help` | aide (commandes, skills) | | `/permissions` | règles chargées, par fichier (badges ALLOW / ASK / DENY) | | `/model` | recharge la liste, change de modèle, conversation conservée ; redemande la taille de contexte | +| `/models` | affiche les modèles de LiteLLM (`listLiteLLMModels`, requête à chaque appel) via `formatModelList` (`cli/src/models-list.ts`) : un par ligne, actif marqué ●, URL interrogée ; erreur réseau/HTTP affichée avec le chemin du settings.json ; ne change rien | | `/clear` | nouvel agent : MAIA.md / CLAUDE.md, skills, outils et permissions rechargés ; messages supprimés ; la session garde son nom | | `/compact` | compactage (§ 14) | | `/rename ` | renomme la session courante | @@ -255,7 +256,7 @@ Ce fichier est dans le dossier de l'**utilisateur** (pas celui de lancement) ; i ## 8. Outils intégrés (`packages/agent/src/tools`) -Ordre de déclaration : Read, Glob, Grep, Write, Edit, Bash, TodoWrite, WebFetch, CurrentModel. Les chemins relatifs sont résolus depuis le dossier de lancement ; `~` et `~/…` désignent le dossier personnel ; un chemin complet est utilisé tel quel. +Ordre de déclaration : Read, Glob, Grep, Write, Edit, Bash, TodoWrite, WebFetch, CurrentModel, puis ListModels (si l'application fournit `listModels` dans le contexte d'initialisation : session principale, sous-agents, agent parent des workflows via `extraTools`). Les chemins relatifs sont résolus depuis le dossier de lancement ; `~` et `~/…` désignent le dossier personnel ; un chemin complet est utilisé tel quel. | Outil | Entrée | Comportement | Permission par défaut | `lockKey` | | --- | --- | --- | --- | --- | @@ -268,6 +269,7 @@ Ordre de déclaration : Read, Glob, Grep, Write, Edit, Bash, TodoWrite, WebFetch | **TodoWrite** | liste de tâches (`pending` · `in_progress` · `completed`, forme `activeForm`) | remplace la liste à chaque appel ; rendu ☐ ▶ ☑ ; avertissement si plusieurs tâches « en cours » | auto | `todowrite` | | **WebFetch** | `url`, `max_length?` | voir ci-dessous | ask | — | | **CurrentModel** | aucune | renvoie le modèle LLM actif (lu à l'appel) | auto | — | +| **ListModels** | aucune | modèles disponibles auprès de LiteLLM (`listLiteLLMModels`, requête à chaque appel), modèle de la session signalé ; erreur réseau renvoyée au modèle ; nom réservé (outil personnalisé homonyme ignoré) | auto | — | **WebFetch.** URL http(s) uniquement. HTML → Markdown via `turndown` (titres `atx`, blocs de code `fenced`, puces `-`, listes à puce « - a » et numérotées « 1. a » par règle `listItem` personnalisée ; balises `head`, `script`, `style`, `noscript`, `iframe`, `svg`, `template` supprimées) ; JSON mis en forme ; texte brut. Limites : **5 Mo**, **30 s**, 5 redirections, `max_length` par défaut **20 000** caractères (plafond **100 000**). Une redirection vers un **autre domaine** n'est pas suivie : l'URL est renvoyée au modèle, qui doit la rappeler (nouvelle confirmation). L'en-tête de résultat contient l'URL, le type, le titre et la mention « Contenu externe non fiable… ». Aucun JavaScript exécuté, ni PDF, ni images. diff --git a/packages/agent/src/commands.ts b/packages/agent/src/commands.ts index a9dc335..286dcf4 100644 --- a/packages/agent/src/commands.ts +++ b/packages/agent/src/commands.ts @@ -4,7 +4,7 @@ import type { SlashCommand } from "@maia/core"; import { parseFrontmatter } from "./frontmatter"; /** Noms réservés aux commandes internes de MAIA (gérées par la CLI). */ -export const RESERVED_COMMANDS = new Set(["help", "clear", "model", "exit", "rename", "load", "new", "delete", "permissions"]); +export const RESERVED_COMMANDS = new Set(["help", "clear", "model", "models", "exit", "rename", "load", "new", "delete", "permissions"]); async function listMarkdown(dir: string, prefix = ""): Promise<{ name: string; file: string }[]> { let entries; diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 4e808d5..1e38054 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -12,6 +12,7 @@ export { type SubAgentInit, } from "./agents"; export { createAgentTool, type SubAgentLauncher } from "./subagent-tool"; +export { createListModelsTool, LIST_MODELS_TOOL_NAME } from "./tools/listmodels"; export { CONTEXT_FILES, loadContextFile } from "./memory"; export { customToolsDir, loadCustomTools } from "./custom-tools"; export { agentsDirFor, applyAgentName, createAgentFromTemplate, validateAgentName, type AgentScope } from "./new-agent"; diff --git a/packages/agent/src/init.ts b/packages/agent/src/init.ts index 2da0616..9d0d206 100644 --- a/packages/agent/src/init.ts +++ b/packages/agent/src/init.ts @@ -7,6 +7,7 @@ import { createPermissionPolicy } from "./permissions"; import { loadPermissionSettings } from "./settings"; import { createSkillTool, loadSkills, skillRoots, skillsPromptSection, toAgentSkills } from "./skills"; import { builtinTools } from "./tools"; +import { LIST_MODELS_TOOL_NAME, createListModelsTool } from "./tools/listmodels"; import { agentsPromptSection, createAgentTool, type SubAgentLauncher } from "./subagent-tool"; /** Contexte d'initialisation de l'agent MAIA : celui du core, plus ce qui concerne les sous-agents. */ @@ -15,6 +16,8 @@ export interface AgentInitContext extends InitContext { launchSubAgent?: SubAgentLauncher; /** Initialisation d'un sous-agent (processus enfant) : définition, outils/skills/permissions restreints. */ subAgent?: SubAgentInit; + /** Fourni par l'application : modèles disponibles auprès de LiteLLM. Absent : pas d'outil ListModels. */ + listModels?: () => Promise; } export interface AgentInitResult extends ModeInitResult { @@ -58,7 +61,7 @@ function buildSystemPrompt( * Initialise l'agent MAIA (il n'y a plus qu'un seul mode) : * - contexte de départ : MAIA.md du dossier de lancement, sinon CLAUDE.md ; avertissement si aucun des deux n'existe ; * - skills : .maia/skills (dossier de lancement, dossier parent, dossier utilisateur) ; - * - outils : Read, Write, Edit, Glob, Grep, Bash, TodoWrite, WebFetch, CurrentModel (+ Skill s'il y a des skills) ; + * - outils : Read, Write, Edit, Glob, Grep, Bash, TodoWrite, WebFetch, CurrentModel, ListModels (si fourni) (+ Skill s'il y a des skills) ; * - outils personnalisés : ./.maia/tools//TOOL.json du dossier de lancement uniquement (ni parent, ni utilisateur). */ export async function initializeAgent(ctx: AgentInitContext): Promise { @@ -75,7 +78,7 @@ export async function initializeAgent(ctx: AgentInitContext): Promise 0) ctx.log(["Skills chargés :", ...skills.map((s) => s.name)].join("\n")); // Outils personnalisés : un sous-dossier de ./.maia/tools par outil (TOOL.json) ; les noms des outils intégrés sont réservés - const reserved = [...builtinTools.map((t) => t.spec.name), "Skill", "Agent"]; + const reserved = [...builtinTools.map((t) => t.spec.name), "Skill", "Agent", LIST_MODELS_TOOL_NAME]; const { tools: customTools, warnings: toolWarnings } = await loadCustomTools(ctx.cwd, reserved, ctx.homeDir); for (const w of toolWarnings) ctx.warn(w); if (customTools.length > 0) ctx.log(["Outils personnalisés chargés :", ...customTools.map((t) => t.spec.name)].join("\n")); @@ -114,7 +117,14 @@ export async function initializeAgent(ctx: AgentInitContext): Promise 0 ? agentsPromptSection(agents) : ""), - tools: [...builtinTools, ...(skills.length > 0 ? [createSkillTool(skills)] : []), ...agentTools, ...customTools, ...mcp.tools], + tools: [ + ...builtinTools, + ...(ctx.listModels ? [createListModelsTool(ctx.listModels)] : []), + ...(skills.length > 0 ? [createSkillTool(skills)] : []), + ...agentTools, + ...customTools, + ...mcp.tools, + ], commands: [], skills: toAgentSkills(skills), permissions: createPermissionPolicy(settings.rules), @@ -144,9 +154,9 @@ async function initializeSubAgent(ctx: AgentInitContext, sub: SubAgentInit): Pro } } - const reserved = [...builtinTools.map((t) => t.spec.name), "Skill", "Agent"]; + const reserved = [...builtinTools.map((t) => t.spec.name), "Skill", "Agent", LIST_MODELS_TOOL_NAME]; const { tools: customTools } = await loadCustomTools(ctx.cwd, reserved, ctx.homeDir); - let tools: Tool[] = [...builtinTools, ...customTools]; + let tools: Tool[] = [...builtinTools, ...(ctx.listModels ? [createListModelsTool(ctx.listModels)] : []), ...customTools]; if (skills.length > 0) tools.push(createSkillTool(skills)); if (sub.tools) { const wanted = new Set(sub.tools.map((t) => t.toLowerCase())); diff --git a/packages/agent/src/tools/listmodels.test.ts b/packages/agent/src/tools/listmodels.test.ts new file mode 100644 index 0000000..16798c5 --- /dev/null +++ b/packages/agent/src/tools/listmodels.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { createListModelsTool } from "./listmodels"; + +describe("ListModels", () => { + it("liste les modèles et signale celui de la session", async () => { + const tool = createListModelsTool(async () => ["claude-haiku", "gpt-4.1"]); + const out = await tool.execute({}, { cwd: "/x", model: "litellm:gpt-4.1" }); + expect(out).toContain("(2)"); + expect(out).toContain("- claude-haiku\n"); + expect(out).toContain("- gpt-4.1 (modèle de la session)"); + }); + it("aucun modèle", async () => { + expect(await createListModelsTool(async () => []).execute({}, { cwd: "/x" })).toContain("Aucun modèle"); + }); + it("erreur du serveur : remontée (l'exécuteur la transmet au modèle)", async () => { + const tool = createListModelsTool(async () => { + throw new Error("LiteLLM injoignable"); + }); + await expect(tool.execute({}, { cwd: "/x" })).rejects.toThrow("injoignable"); + }); + it("sans confirmation, sans paramètre", () => { + const tool = createListModelsTool(async () => []); + expect(tool.permission).toBe("auto"); + expect(tool.spec.name).toBe("ListModels"); + }); +}); diff --git a/packages/agent/src/tools/listmodels.ts b/packages/agent/src/tools/listmodels.ts new file mode 100644 index 0000000..3eca568 --- /dev/null +++ b/packages/agent/src/tools/listmodels.ts @@ -0,0 +1,32 @@ +import type { Tool } from "@maia/core"; + +export const LIST_MODELS_TOOL_NAME = "ListModels"; + +/** + * Liste des modèles disponibles auprès de LiteLLM (même source que /models). La requête est fournie par l'application + * (le paquet agent ne connaît pas LiteLLM) : elle est refaite à chaque appel. + */ +export function createListModelsTool(listModels: () => Promise): Tool { + return { + spec: { + name: LIST_MODELS_TOOL_NAME, + description: + "Renvoie la liste des modèles LLM disponibles auprès du serveur LiteLLM, en signalant le modèle de la session. " + + "À utiliser quand on te demande quels modèles sont disponibles ou utilisables : ne réponds pas de mémoire. " + + "Pour changer de modèle, l'utilisateur tape /model.", + inputSchema: { type: "object", properties: {} }, + }, + permission: "auto", + describe: () => "modèles disponibles", + async execute(_input, ctx) { + const names = await listModels(); + if (names.length === 0) return "Aucun modèle déclaré par le serveur LiteLLM."; + const active = ctx.model?.startsWith("litellm:") ? ctx.model.slice("litellm:".length) : ctx.model; + return [ + `Modèles disponibles auprès de LiteLLM (${names.length}) :`, + ...names.map((n) => `- ${n}${n === active ? " (modèle de la session)" : ""}`), + "Changement de modèle : commande /model (par l'utilisateur).", + ].join("\n"); + }, + }; +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 7862805..3a25473 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -35,7 +35,8 @@ import { type AgentScope, type McpServerStatus, } from "@maia/agent"; -import { createProvider, loadModelChoices, loadSettings, settingsPath } from "@maia/providers"; +import { createProvider, joinUrl, listLiteLLMModels, loadModelChoices, loadSettings, settingsPath } from "@maia/providers"; +import { formatModelList } from "./models-list"; import { InkUI } from "@maia/ui"; import { resolveConfig } from "./config"; import { resolveLaunchDir } from "./launch-dir"; @@ -90,7 +91,7 @@ Usage : maia [options] -h, --help Affiche cette aide run Lance un workflow autonome sans interface (voir plus bas : maia run --help) -Commandes en session : /help /model /permissions /mcp /workflows /agents /createAgent /clear /compact /exit +Commandes en session : /help /model /models /permissions /mcp /workflows /agents /createAgent /clear /compact /exit Sessions : /rename /load [nom] /new /delete (sauvegarde automatique à la sortie, dans ./.maia/.sessions) /delete : Espace coche/décoche des sessions, Suppr les supprime du disque (après confirmation) Skills de l'agent : §nom_du_skill [arguments] (§ seul : liste des skills) @@ -226,6 +227,7 @@ async function run(ui: InkUI, flags: { model?: string }): Promise { warn: (m) => ui.warn(m), startContext: opts.startContext, launchSubAgent, + listModels: async () => (await listLiteLLMModels(settings)).map((c) => c.label), }); agents = init.agents; const previousClose = closeMcp; @@ -747,6 +749,24 @@ async function run(ui: InkUI, flags: { model?: string }): Promise { await compactNow(); continue; } + if (name === "models") { + // Liste seule (aucun changement de modèle) : interroge LiteLLM à chaque appel + let names: string[]; + try { + names = ( + await ui.withSpinner("Chargement des modèles disponibles…", () => listLiteLLMModels(settings), "Modèles chargés") + ).map((c) => c.label); + } catch (err) { + ui.error(`${(err as Error).message}\nVérifiez LiteLLM_URL et LiteLLM_Models dans ${settingsPath(homeDir)}.`); + continue; + } + if (names.length === 0) { + ui.warn(`LiteLLM ne déclare aucun modèle (${joinUrl(settings.LiteLLM_URL, settings.LiteLLM_Models)}).`); + continue; + } + ui.info(formatModelList(names, modelSpec, joinUrl(settings.LiteLLM_URL, settings.LiteLLM_Models))); + continue; + } if (name === "model") { // Recharge la liste (les déploiements ont pu changer) ; la conversation en cours est conservée. const choice = await pickModel(modelSpec); diff --git a/packages/cli/src/models-list.test.ts b/packages/cli/src/models-list.test.ts new file mode 100644 index 0000000..6aa0016 --- /dev/null +++ b/packages/cli/src/models-list.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { formatModelList } from "./models-list"; + +describe("formatModelList (/models)", () => { + it("un modèle par ligne, le modèle actif signalé, nombre et URL en tête", () => { + const text = formatModelList(["claude-sonnet", "gpt-4o"], "litellm:gpt-4o", "http://localhost:4000/v1/models"); + expect(text.split("\n")).toEqual([ + "Modèles disponibles auprès de LiteLLM (2) — http://localhost:4000/v1/models", + " ○ claude-sonnet", + " ● gpt-4o (actif)", + "/model : changer de modèle", + ]); + }); + + it("modèle actif absent de la liste : aucun n'est signalé", () => { + expect(formatModelList(["a"], "litellm:b", "u")).not.toContain("actif"); + }); +}); diff --git a/packages/cli/src/models-list.ts b/packages/cli/src/models-list.ts new file mode 100644 index 0000000..bdc19cb --- /dev/null +++ b/packages/cli/src/models-list.ts @@ -0,0 +1,8 @@ +// Affichage de /models : liste des modèles disponibles auprès de LiteLLM (sans rien changer à la session). + +/** Texte affiché par /models : un modèle par ligne, le modèle actif signalé. */ +export function formatModelList(names: string[], current: string | undefined, url: string): string { + const active = current?.startsWith("litellm:") ? current.slice("litellm:".length) : current; + const lines = names.map((n) => (n === active ? ` ● ${n} (actif)` : ` ○ ${n}`)); + return [`Modèles disponibles auprès de LiteLLM (${names.length}) — ${url}`, ...lines, "/model : changer de modèle"].join("\n"); +} diff --git a/packages/cli/src/subagent-child.ts b/packages/cli/src/subagent-child.ts index 0dd5939..7c4e95c 100644 --- a/packages/cli/src/subagent-child.ts +++ b/packages/cli/src/subagent-child.ts @@ -7,7 +7,7 @@ import { type Message, } from "@maia/core"; import { EFFORT_ITERATIONS, initializeAgent } from "@maia/agent"; -import { createProvider, loadSettings } from "@maia/providers"; +import { createProvider, listLiteLLMModels, loadSettings } from "@maia/providers"; import type { ChildMessage, ChildSpec, ParentMessage } from "./subagent-protocol"; // Processus d'un sous-agent : démarré par le parent avec `maia --subagent`, il reçoit sa mission par IPC, travaille avec @@ -93,6 +93,7 @@ export async function runSubAgentChild(): Promise { log: () => undefined, warn: (message) => void send({ type: "log", level: "warn", message }), subAgent: spec.agent, + listModels: async () => (await listLiteLLMModels(settings)).map((c) => c.label), }); const session = createSession({ provider: createProvider(spec.model, settings), init, ui, cwd: spec.cwd }); messages = session.messages; diff --git a/packages/cli/src/workflow-run.ts b/packages/cli/src/workflow-run.ts index 09911c9..0b92ae0 100644 --- a/packages/cli/src/workflow-run.ts +++ b/packages/cli/src/workflow-run.ts @@ -20,11 +20,12 @@ import { } from "@maia/core"; import { INTERVENTION_TOOL_NAME, + createListModelsTool, initializeOrchestrator, loadOrchestrator, resolveSubAgentModel, } from "@maia/agent"; -import { createProvider, loadModelChoices, loadSettings, type MaiaSettings } from "@maia/providers"; +import { createProvider, listLiteLLMModels, loadModelChoices, loadSettings, type MaiaSettings } from "@maia/providers"; import { createSubAgentLauncher } from "./subagent-parent"; /** Délai maximal d'un sous-agent dans un workflow : celui du workflow (un sous-agent peut attendre une autorisation longtemps). */ @@ -342,7 +343,10 @@ export async function runWorkflow(opts: WorkflowRunOptions, deps: WorkflowRunDep def, homeDir: opts.homeDir, launchSubAgent, - extraTools: [interventionTool], + extraTools: [ + interventionTool, + createListModelsTool(async () => (await listLiteLLMModels(settings)).map((c) => c.label)), + ], log: (m) => say(parent, "info", m), warn: (m) => say(parent, "warn", m), });