mini-me depuis Node.js : minime.js
Un client Node.js qui utilise mini-me comme bibliothèque en pilotant mm -acp par ACP (ticket 167). Un fichier, minime.js, aucune dépendance (Node ≥ 20 : child_process et readline). Voir ../README.fr.md pour les messages ACP utilisés.
import { Agent } from "./minime.js";
const agent = await Agent.start({ config: "agent.yaml" });
const { text } = await agent.complete("Explain this repository"); // sans stream
for await (const ev of agent.stream("And the tests?")) { // en stream
if (ev.kind === "text") process.stdout.write(ev.text);
}
const ac = new AbortController();
setTimeout(() => ac.abort(), 3000);
await agent.complete("A long task", { signal: ac.signal }); // annulé
await agent.close();
Lancer les exemples
llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
llmman serve
cd demo.acp.sdk/nodejs
node examples/complete.js # sans stream
node examples/stream.js # en stream, avec les appels d'outils
node examples/cancel.js # annulé au bout de 3 secondes
node examples/permission.js # une commande refusée, puis autorisée
mm doit être dans le PATH, ou désigné par MM_BIN. Les exemples utilisent agent.yaml (Qwen3.5 4B sur llmman, bash seulement, yolo: false) ; tout fichier d'agent convient.
L'API
await Agent.start({ config, cwd?, mm?, onPermission?, onStderr? }) |
lance mm -acp -config <config> (rendu absolu), puis initialize et session/new {cwd}. mm vaut par défaut $MM_BIN, puis mm. |
await agent.complete(prompt, { signal?, onEvent? }) |
un tour ; renvoie { text, stopReason, usage } une fois terminé. onEvent voit chaque événement au fil de l'eau. |
agent.stream(prompt, { signal? }) |
un tour sous forme d'itérateur asynchrone d'événements ; le dernier est done. Sortir de la boucle avant la fin annule le tour. |
signal (un AbortSignal) |
l'annuler envoie session/cancel ; le tour se termine avec stopReason: "cancelled" et le texte déjà reçu. |
onPermission(call) |
appelé quand l'agent veut lancer un outil qui demande une confirmation (yolo: false). Renvoyer true (autoriser une fois), false (refuser une fois) ou un optionId ACP ("allow_always", "never"). Sans lui, tous ces appels sont refusés. |
onStderr(line) |
la sortie d'erreur de mm (bannière, avertissements, lignes [acp]). Sinon elle est lue et ignorée ; sa dernière ligne est ajoutée à l'erreur quand mm s'arrête. |
await agent.reset() |
une nouvelle session sur le même processus : la conversation repart de zéro. |
await agent.close() |
ferme l'entrée standard de mm et attend sa fin. |
agent.sessionId |
l'identifiant de session ACP. |
Les événements : {kind: "text", text}, {kind: "thinking", text}, {kind: "tool_call", id, title, toolKind, input}, {kind: "tool_result", id, status, output}, {kind: "done", stopReason, usage, text}.
À savoir :
- Un Agent = un processus mm = une conversation. Les appels la poursuivent, comme dans un éditeur. Un seul tour à la fois par Agent (un second lève
already running) ; lancez plusieurs Agents pour travailler en parallèle. - Un tour que mm termine en erreur est rejeté avec les mots de mm, par exemple
minime: Internal error (-32603): {"error":"the model repeated the same bash call 6 times…"}. - Une question annulée est retirée de la conversation par mm : la question suivante n'en sait rien.
- Le client n'annonce aucune capacité fichiers, terminal ni formulaire : mm n'en a pas besoin, et son outil
ask_usern'est pas proposé.
Tests
npm test # node --test test/minime.test.js — 11 tests, sans modèle, sans mm
test/fake-mm.js joue le rôle de mm dans ACP, avec les formes de messages capturées sur mm v1.0.1 : des réponses, un appel d'outil avec sa permission, un tour lent à annuler, un plantage. Pas de node --test tout court : il lancerait aussi fake-mm.js comme un test, qui attendrait sans fin.
Mesuré (2026-10-26, llmman sur la machine de l'auteur, mm v1.0.1 compilé depuis l'arbre de travail)
| Exemple | Résultat |
|---|---|
complete.js |
réponse en une phrase, end_turn, 663 tokens, 2,9 s |
stream.js |
find examples -name "*.js" | wc -l autorisé par le callback (lecture seule), sortie 4, réponse affichée morceau par morceau, end_turn |
cancel.js |
histoire coupée au bout de 3,0 s (cancelled, 383 caractères gardés) ; la question suivante sur le même Agent a répondu tout de suite — sans connaître l'histoire, que mm a retirée |
permission.js |
pwd refusé : le modèle a inventé une réponse (/root) ; autorisé : le vrai dossier. Un refus n'empêche pas un petit modèle de deviner. |
Vu sur trois passages, avant que le callback n'autorise find : le modèle a relancé la même commande refusée jusqu'à ce que le détecteur de boucle de mm arrête le tour (6 appels identiques) ; stream() a été rejeté avec ce message — d'où le try/catch de stream.js.
Pas fait
- La bibliothèque ACP TypeScript officielle n'a pas été essayée (pas de réseau dans le bac à sable où ceci a été écrit) : du JSON-RPC brut garde le client sans dépendance.
- Pas de paquet npm publié : il faudrait livrer le binaire
mmavec, par plateforme. session/load/session/resume(sessions enregistrées), images et fichiers joints dans les prompts, formulairesask_user: non exposés.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
|