bots-garden/mini-mepublic Fork
⑂ v1.0.4
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. eca9ec1 · on v1.0.4 · k33g · 7d ago
README.en.md · 98 lines · 6.5 KBmarkdown
Blame HistoryOpen raw

mini-me from Node.js, with the official ACP SDK: lib/minime.js

Version française

A Node.js client that uses mini-me as a library by driving mm -acp over ACP (ticket 188), built on the official ACP TypeScript SDK, @agentclientprotocol/sdk 2.2.0. It has the same API as the dependency-free client in ../nodejs/, which speaks JSON-RPC by hand: the examples of both folders are the same programs. The library is JavaScript (ESM) with JSDoc types, so there is no build step. See ../README.en.md for the ACP messages behind it.

import { Agent } from "./lib/minime.js";

const agent = await Agent.start({ config: "agent.yaml" });

const { text } = await agent.complete("Explain this repository");      // not streamed

for await (const ev of agent.stream("And the tests?")) {               // streamed
  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 });           // cancelled

await agent.close();

Run the examples

llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
llmman serve

cd demo.acp.sdk/nodejs-sdk
npm install           # the SDK, and zod which it requires
node complete.js      # not streamed
node stream.js        # streamed, with the tool calls
node cancel.js        # cancelled after 3 seconds
node permission.js    # a command refused, then allowed

Node 20 or later. mm must be on the PATH, or named by MM_BIN. The examples use agent.yaml (Qwen3.5 4B on llmman, bash only, yolo: false); any agent file works.

To use the library from another project, until it is published: npm install ../path/to/demo.acp.sdk/nodejs-sdk, then import { Agent } from "minime-sdk";.

The API

await Agent.start({ config, cwd?, mm?, onPermission?, onStderr? }) starts mm -acp -config <config> (made absolute), then initialize and session/new {cwd}. mm defaults to $MM_BIN, then mm.
await agent.complete(prompt, { signal?, onEvent? }) one turn; resolves to { text, stopReason, usage } once it is over. onEvent sees every event as it comes.
agent.stream(prompt, { signal? }) one turn as an async iterator of events; the last one is done. Leaving the loop early cancels the turn.
signal (an AbortSignal) aborting it sends session/cancel; the turn ends with stopReason: "cancelled" and the text so far.
onPermission(call) asked when the agent wants to run a tool that needs a confirmation (yolo: false). Return true (allow once), false (reject once) or an ACP optionId ("allow_always", "never"). Without it, every such call is refused. A callback that throws refuses.
onStderr(line) mm's stderr (banner, warnings, [acp] lines). Otherwise it is read and dropped; its last line is added to errors.
await agent.reset() a new session on the same process: the conversation starts over.
await agent.close() closes mm's stdin and waits for it to exit (kills it after 5 s). Calling it twice is harmless.
agent.sessionId the ACP session id.
eventOf(update), pickOption(options, choice) the two pure helpers, exported for tests and for anyone handling ACP messages themselves.

The events: {kind: "text", text}, {kind: "thinking", text}, {kind: "tool_call", id, title, toolKind, input}, {kind: "tool_result", id, status, output}, {kind: "done", stopReason, usage, text}.

Rules worth knowing:

  • One Agent = one mm process = one conversation. Calls continue it, as in an editor. One turn at a time per Agent (a second one throws already running); start several Agents to work in parallel.
  • A turn mm ends with an error rejects with mm's own words and its last stderr line, e.g. minime: Internal error (-32603): the model repeated the same bash call 6 times…. If mm crashed, the message says mm exited: exit status N.
  • A cancelled question is dropped from the conversation by mm: the next question does not know about it.
  • The permission callback can run before the tool_call event is read. The SDK answers mm's requests as soon as they arrive, while the session's updates wait in a queue until your loop reads them: stream.js prints 🔐 before 🛠️. The dependency-free client reads both in arrival order.
  • The client advertises no file-system, terminal or form capability: mm needs none, and its ask_user tool is not offered. The SDK answers any such request from mm with "method not found".

How it differs from ../nodejs/

nodejs/ nodejs-sdk/
Protocol JSON-RPC written by hand (~300 lines) the official SDK: client(), ndJsonStream, buildSession(cwd).start(), ActiveSession.nextUpdate()
Dependencies none @agentclientprotocol/sdk 2.2.0 and zod (its peer dependency)
Message validation none the SDK checks the messages against the ACP schema
API Agent.start, complete, stream, reset, close the same

Tests

npm test        # node --test test/*.test.js — 16 tests, no model, no mm, ~1 s

test/fake-mm.js plays mm's side of ACP as a real child process. It writes and reads the JSON lines itself, with the message shapes mm v1.0.3 sends (four permission options, rawOutput.output, usage on the prompt's answer, available_commands_update after session/new): answers, a thought, a tool call with its permission, a slow turn to cancel, an error, a crash.

Measured (2026-11-01, llmman on the author's host, mm v1.0.3 built from the working tree, Node 22)

Example Result
complete.js one-sentence answer, end_turn, 663 tokens, 7.3 s
stream.js find test -name "*.js" | wc -l allowed by the callback (read-only), output 2, answer streamed chunk by chunk, end_turn
cancel.js story cut after 3.0 s (cancelled, 366 characters kept); the next question on the same Agent answered at once, without knowing the story, which mm dropped
permission.js pwd refused: the example prints nothing (the model invented /root, 3 runs out of 3); allowed: the real directory

Not done

  • Not a published npm package: the mm binary would have to ship with it, per platform.
  • session/load / session/resume (stored sessions), images and file attachments in prompts, ask_user forms: not exposed.
  • No timeout on a cancel mm never answers: the turn waits for mm's cancelled answer, as ACP requires.
 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
87
88
89
90
91
92
93
94
95
96
97
98
# mini-me from Node.js, with the official ACP SDK: `lib/minime.js`

*[Version française](README.fr.md)*

A Node.js client that uses mini-me as a library by driving `mm -acp` over ACP (ticket 188), built on the official ACP TypeScript SDK, [`@agentclientprotocol/sdk`](https://www.npmjs.com/package/@agentclientprotocol/sdk) 2.2.0. It has **the same API** as the dependency-free client in [`../nodejs/`](../nodejs/README.en.md), which speaks JSON-RPC by hand: the examples of both folders are the same programs. The library is JavaScript (ESM) with JSDoc types, so there is no build step. See [`../README.en.md`](../README.en.md) for the ACP messages behind it.

```js
import { Agent } from "./lib/minime.js";

const agent = await Agent.start({ config: "agent.yaml" });

const { text } = await agent.complete("Explain this repository");      // not streamed

for await (const ev of agent.stream("And the tests?")) {               // streamed
  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 });           // cancelled

await agent.close();
```

## Run the examples

```bash
llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
llmman serve

cd demo.acp.sdk/nodejs-sdk
npm install           # the SDK, and zod which it requires
node complete.js      # not streamed
node stream.js        # streamed, with the tool calls
node cancel.js        # cancelled after 3 seconds
node permission.js    # a command refused, then allowed
```

Node 20 or later. `mm` must be on the PATH, or named by `MM_BIN`. The examples use `agent.yaml` (Qwen3.5 4B on llmman, bash only, `yolo: false`); any agent file works.

To use the library from another project, until it is published: `npm install ../path/to/demo.acp.sdk/nodejs-sdk`, then `import { Agent } from "minime-sdk";`.

## The API

| | |
|---|---|
| `await Agent.start({ config, cwd?, mm?, onPermission?, onStderr? })` | starts `mm -acp -config <config>` (made absolute), then `initialize` and `session/new {cwd}`. `mm` defaults to `$MM_BIN`, then `mm`. |
| `await agent.complete(prompt, { signal?, onEvent? })` | one turn; resolves to `{ text, stopReason, usage }` once it is over. `onEvent` sees every event as it comes. |
| `agent.stream(prompt, { signal? })` | one turn as an async iterator of events; the last one is `done`. Leaving the loop early cancels the turn. |
| `signal` (an `AbortSignal`) | aborting it sends `session/cancel`; the turn ends with `stopReason: "cancelled"` and the text so far. |
| `onPermission(call)` | asked when the agent wants to run a tool that needs a confirmation (`yolo: false`). Return `true` (allow once), `false` (reject once) or an ACP `optionId` (`"allow_always"`, `"never"`). **Without it, every such call is refused.** A callback that throws refuses. |
| `onStderr(line)` | mm's stderr (banner, warnings, `[acp]` lines). Otherwise it is read and dropped; its last line is added to errors. |
| `await agent.reset()` | a new session on the same process: the conversation starts over. |
| `await agent.close()` | closes mm's stdin and waits for it to exit (kills it after 5 s). Calling it twice is harmless. |
| `agent.sessionId` | the ACP session id. |
| `eventOf(update)`, `pickOption(options, choice)` | the two pure helpers, exported for tests and for anyone handling ACP messages themselves. |

The events: `{kind: "text", text}`, `{kind: "thinking", text}`, `{kind: "tool_call", id, title, toolKind, input}`, `{kind: "tool_result", id, status, output}`, `{kind: "done", stopReason, usage, text}`.

Rules worth knowing:

- **One Agent = one mm process = one conversation.** Calls continue it, as in an editor. One turn at a time per Agent (a second one throws `already running`); start several Agents to work in parallel.
- **A turn mm ends with an error rejects** with mm's own words and its last stderr line, e.g. `minime: Internal error (-32603): the model repeated the same bash call 6 times…`. If mm crashed, the message says `mm exited: exit status N`.
- **A cancelled question is dropped from the conversation by mm**: the next question does not know about it.
- **The permission callback can run before the `tool_call` event is read.** The SDK answers mm's requests as soon as they arrive, while the session's updates wait in a queue until your loop reads them: `stream.js` prints 🔐 before 🛠️. The dependency-free client reads both in arrival order.
- The client advertises no file-system, terminal or form capability: mm needs none, and its `ask_user` tool is not offered. The SDK answers any such request from mm with "method not found".

## How it differs from `../nodejs/`

| | `nodejs/` | `nodejs-sdk/` |
|---|---|---|
| Protocol | JSON-RPC written by hand (~300 lines) | the official SDK: `client()`, `ndJsonStream`, `buildSession(cwd).start()`, `ActiveSession.nextUpdate()` |
| Dependencies | none | `@agentclientprotocol/sdk` 2.2.0 and `zod` (its peer dependency) |
| Message validation | none | the SDK checks the messages against the ACP schema |
| API | `Agent.start`, `complete`, `stream`, `reset`, `close` | the same |

## Tests

```bash
npm test        # node --test test/*.test.js — 16 tests, no model, no mm, ~1 s
```

`test/fake-mm.js` plays mm's side of ACP as a real child process. It writes and reads the JSON lines itself, with the message shapes mm v1.0.3 sends (four permission options, `rawOutput.output`, usage on the prompt's answer, `available_commands_update` after `session/new`): answers, a thought, a tool call with its permission, a slow turn to cancel, an error, a crash.

## Measured (2026-11-01, llmman on the author's host, mm v1.0.3 built from the working tree, Node 22)

| Example | Result |
|---|---|
| `complete.js` | one-sentence answer, `end_turn`, 663 tokens, **7.3 s** |
| `stream.js` | `find test -name "*.js" \| wc -l` allowed by the callback (read-only), output `2`, answer streamed chunk by chunk, `end_turn` |
| `cancel.js` | story cut after **3.0 s** (`cancelled`, 366 characters kept); the next question on the same Agent answered at once, without knowing the story, which mm dropped |
| `permission.js` | `pwd` refused: the example prints nothing (the model **invented** `/root`, 3 runs out of 3); allowed: the real directory |

## Not done

- Not a published npm package: the `mm` binary would have to ship with it, per platform.
- `session/load` / `session/resume` (stored sessions), images and file attachments in prompts, `ask_user` forms: not exposed.
- No timeout on a cancel mm never answers: the turn waits for mm's `cancelled` answer, as ACP requires.