pi-lemonade-link 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +598 -0
- package/docs/README.md +22 -0
- package/docs/anthropic.md +11 -0
- package/docs/lemonade.md +2537 -0
- package/docs/llamacpp.md +442 -0
- package/docs/mcp.md +199 -0
- package/docs/ollama.md +21 -0
- package/docs/openai.md +1192 -0
- package/index.ts +3245 -0
- package/lemonade.example.json +85 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# pi-lemonade-link
|
|
2
|
+
|
|
3
|
+
A [pi](https://github.com/earendil-works/pi-mono) extension that connects pi to a
|
|
4
|
+
self-hosted [Lemonade](https://github.com/lemonade-sdk/lemonade) server — a local
|
|
5
|
+
LLM/audio/image server with GPU acceleration — and keeps that connection in sync
|
|
6
|
+
with whatever the server is actually doing.
|
|
7
|
+
|
|
8
|
+
No hardcoded endpoints, no manual model lists. One extension, five capabilities:
|
|
9
|
+
|
|
10
|
+
1. **Live model discovery** — every chat model your lemonade box advertises shows
|
|
11
|
+
up in `/model` automatically, annotated with whether it's loaded right now.
|
|
12
|
+
2. **Nine agent tools** — transcription, image generation/editing/upscaling,
|
|
13
|
+
text-to-speech, music/SFX, 3D meshes, and text classification, all callable
|
|
14
|
+
by the LLM, all model-aware.
|
|
15
|
+
3. **A setup menu** — `/lemonade-setup` opens a navigable TUI for status,
|
|
16
|
+
discovery, configuration, model management (live-progress pulls, Hugging
|
|
17
|
+
Face installs), and live log streaming.
|
|
18
|
+
4. **A lemonade status bar** — while a lemonade model is active, an extra
|
|
19
|
+
below-editor row appears (your footer is never touched): instance name,
|
|
20
|
+
tok/s and prefix-cache hit % derived locally from the session's own
|
|
21
|
+
messages, plus polled busy/queue, CPU/GPU/NPU and VRAM from the active
|
|
22
|
+
instance.
|
|
23
|
+
Switching to a non-lemonade model removes the row.
|
|
24
|
+
5. **Harness self-awareness** — every chat, including virgin ones, is told
|
|
25
|
+
where its lemonade machinery lives, so the agent can operate it directly.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why this exists
|
|
30
|
+
|
|
31
|
+
Lemonade is a *server*, and its contents change constantly: you load models, unload
|
|
32
|
+
them, pull new ones. pi, by contrast, learns about models from a static file. The
|
|
33
|
+
job of this extension is to erase that gap: every time pi starts, it asks lemonade
|
|
34
|
+
what it currently has, and registers exactly that.
|
|
35
|
+
|
|
36
|
+
## Design philosophy — why this is built pi-native
|
|
37
|
+
|
|
38
|
+
This extension is not just a feature bridge; every structural choice reflects
|
|
39
|
+
how pi itself works:
|
|
40
|
+
|
|
41
|
+
- **Dynamic truth over static files.** pi learns providers from a static file;
|
|
42
|
+
this extension re-asks the server at every startup and re-registers, so
|
|
43
|
+
`/model` always mirrors reality — down to the `loaded` / `on-demand`
|
|
44
|
+
annotations, which come from live health state, never guesses.
|
|
45
|
+
- **Native tools, not protocol shims.** Every capability is a plain
|
|
46
|
+
`pi.registerTool()` function with a `promptSnippet` — pi's own advertisement
|
|
47
|
+
mechanism, flattened by the harness into the provider's dialect. MCP appears
|
|
48
|
+
only where it's genuinely MCP: lemonade's own gateway ships as an optional,
|
|
49
|
+
disabled stdio entry for *other* MCP-consuming setups. Trusted in-process
|
|
50
|
+
code is never wrapped in JSON-RPC — that would add lifecycle overhead and
|
|
51
|
+
couple failure domains for zero benefit.
|
|
52
|
+
- **Harness-level self-awareness.** The model has no inherent knowledge of the
|
|
53
|
+
machinery it runs on — the harness must *tell* it, in the one place it reads
|
|
54
|
+
every turn. The `promptGuidelines` lines inject the machinery's location
|
|
55
|
+
and the configured instance list into every chat's system prompt, so
|
|
56
|
+
"what's loaded on your server?" resolves deterministically in a virgin
|
|
57
|
+
chat instead of by lucky path-hunting.
|
|
58
|
+
- **Advertisement that reflects reality.** Tool snippets use a `[lemonade]`
|
|
59
|
+
prefix (a corpus-bucket convention, so tools group visibly by origin in the
|
|
60
|
+
prompt), and `classify_text`'s description is *generated per session* from
|
|
61
|
+
the live catalog — the advertisement enumerates the classifiers that
|
|
62
|
+
actually exist on the box, and refreshes itself on every startup.
|
|
63
|
+
- **Everything configurable, nothing hardcoded.** Every endpoint is a path key
|
|
64
|
+
in `lemonade.json`. Moving the server, changing ports, or tracking upstream
|
|
65
|
+
API changes is config, not code.
|
|
66
|
+
- **Live state where it matters.** Pulls stream real progress from
|
|
67
|
+
server-owned download jobs with Esc-to-cancel; the status panel streams the
|
|
68
|
+
full state of the box — every metric from five endpoints, grouped by source,
|
|
69
|
+
re-fetched live while open, cyclable across instances with `n`/`p`; the
|
|
70
|
+
status bar surfaces per-turn tok/s, cache hits, box busyness and VRAM while
|
|
71
|
+
you chat, appearing only when a lemonade model is active; logs stream over
|
|
72
|
+
websocket. Destructive actions gate behind confirm dialogs and a typed
|
|
73
|
+
phrase.
|
|
74
|
+
- **Failures speak.** Missing models produce errors naming what to pull — the
|
|
75
|
+
agent relays and recovers. Never a silent failure.
|
|
76
|
+
- **One entry point.** A single command — `/lemonade-setup` — every capability
|
|
77
|
+
navigable from one TUI menu; no command sprawl.
|
|
78
|
+
- **Ground truth snapshotted.** `docs/` holds the official API spec so this
|
|
79
|
+
extension can be audited against it offline, and the behavior reference
|
|
80
|
+
records what was empirically confirmed on which server build.
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
1. Make sure your lemonade server is running and reachable.
|
|
85
|
+
2. Configure an instance — either way works:
|
|
86
|
+
- **Zero-touch:** just start pi. If `~/.pi/agent/lemonade.json` doesn't
|
|
87
|
+
exist, the extension creates a minimal, commented blank one (zero
|
|
88
|
+
servers) and shows an info notice pointing you at `/lemonade-setup`,
|
|
89
|
+
which opens a **first-run wizard**: *Discover servers* (UDP beacon +
|
|
90
|
+
HTTP fallback) or *Add instance manually*. Once the first instance
|
|
91
|
+
exists, everything registers and the normal menu takes over — no
|
|
92
|
+
restart needed.
|
|
93
|
+
- **By hand:** copy the fully-commented `lemonade.example.json` to
|
|
94
|
+
`~/.pi/agent/lemonade.json` and point the first `servers` entry's
|
|
95
|
+
`baseUrl` at your server. Nothing is hardcoded, so the file is the
|
|
96
|
+
source of truth — a config that exists but is unparseable or invalid
|
|
97
|
+
still fails loudly (red error, nothing registered).
|
|
98
|
+
3. Start pi. Every chat-capable model on the server appears in `/model` as
|
|
99
|
+
`lemonade-<instance-name>/<model-id>` (e.g. `lemonade-main/Qwen3-8B-GGUF`),
|
|
100
|
+
labeled `loaded` or `on-demand`.
|
|
101
|
+
4. Pick one and chat. Models marked *on-demand* are auto-loaded by lemonade when
|
|
102
|
+
first requested — if the target slot is occupied by a pinned model, lemonade
|
|
103
|
+
replies with a `slots_pinned_error` you'll see as an error message.
|
|
104
|
+
5. Say *"transcribe `/path/to/file.mp3`"* in any session — the agent calls
|
|
105
|
+
`transcribe_audio` and Whisper returns the text.
|
|
106
|
+
6. Type `/lemonade-setup` to manage everything interactively.
|
|
107
|
+
|
|
108
|
+
## How the pieces work
|
|
109
|
+
|
|
110
|
+
### Model discovery
|
|
111
|
+
|
|
112
|
+
At every startup (and after `/reload`, and after any change made in the setup
|
|
113
|
+
menu), the extension queries two endpoints:
|
|
114
|
+
|
|
115
|
+
- `GET /v1/models` — the catalog. Each entry carries rich metadata:
|
|
116
|
+
`labels` (chat, reasoning, vision, transcription, image…), `recipe`,
|
|
117
|
+
`max_context_window`, `downloaded`, size.
|
|
118
|
+
- `GET /v1/health` — live state: which models are *actually* loaded, per-type
|
|
119
|
+
slot limits, the realtime websocket port.
|
|
120
|
+
|
|
121
|
+
It then registers one pi provider per configured instance, uniformly named
|
|
122
|
+
`lemonade-<instance-name>`, with one pi model per catalog entry, mapped as
|
|
123
|
+
follows:
|
|
124
|
+
|
|
125
|
+
| Lemonade says | pi registers |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `labels` contains `chat` (or `recipe: llamacpp`) | included (by default; see *Chat-only filter*) |
|
|
128
|
+
| `labels` contains `reasoning` | `reasoning: true` |
|
|
129
|
+
| `labels` contains `vision` | image input enabled |
|
|
130
|
+
| `max_context_window` | context window |
|
|
131
|
+
| model appears in `/health` | name suffix `(lemonade-<instance>, loaded)` |
|
|
132
|
+
| not in `/health` | name suffix `(lemonade-<instance>, on-demand)` |
|
|
133
|
+
|
|
134
|
+
If the server is unreachable at startup, the provider registers **nothing** —
|
|
135
|
+
an unreachable model is not listable, so `/model` gets no lemonade entries
|
|
136
|
+
until the box is reachable. (Consequence, accepted: sessions pinned to a
|
|
137
|
+
lemonade-<name> model hard-fail model resolution while offline — "Model not found" —
|
|
138
|
+
and recover on the next startup or `/lemonade-setup` → Refresh once the box
|
|
139
|
+
is back.)
|
|
140
|
+
|
|
141
|
+
**Chat-only filter** — lemonade also hosts transcription (Whisper), image
|
|
142
|
+
generation (Flux), and other non-chat models. pi can only drive chat-completions
|
|
143
|
+
models, so those are filtered out by default. Toggle via *Model management →
|
|
144
|
+
Toggle chat-only filter* if you want them listed anyway.
|
|
145
|
+
|
|
146
|
+
**Compat flags** — the provider is registered with lemonade-appropriate settings
|
|
147
|
+
(`max_tokens` instead of `max_completion_tokens`, `system` instead of `developer`
|
|
148
|
+
role, no `reasoning_effort`), so requests work out of the box.
|
|
149
|
+
|
|
150
|
+
### Agent tools
|
|
151
|
+
|
|
152
|
+
The extension registers nine tools the LLM can call on your behalf. Two facts
|
|
153
|
+
apply to all of them:
|
|
154
|
+
|
|
155
|
+
**Instance selection.** Every tool takes an optional `server` argument naming
|
|
156
|
+
a configured instance (omit for the default). `"default"` is kept as a
|
|
157
|
+
reserved alias; the default's actual name works too. Unknown names produce an
|
|
158
|
+
error listing available instances. See *Multiple lemonade instances*.
|
|
159
|
+
|
|
160
|
+
**Model selection precedence.** Tools that need a model resolve it in this
|
|
161
|
+
order: (1) an explicit `model` argument in the tool call, (2) the
|
|
162
|
+
extension's configured default (`defaultImageModel`, `defaultUpscaleModel`,
|
|
163
|
+
`defaultTranscriptionModel`, `defaultClassifierModel` in `lemonade.json`),
|
|
164
|
+
(3) auto-pick — the first catalog model carrying the right label (`image`,
|
|
165
|
+
`edit`, `tts`, ...) or recipe (`onnxruntime` for classifiers). Set a default
|
|
166
|
+
in `/lemonade-setup` or config if you want a specific model to win.
|
|
167
|
+
|
|
168
|
+
**How results come back.** Text results (transcripts) are returned inline into
|
|
169
|
+
the conversation. Binary outputs (images, audio, meshes) are *written to
|
|
170
|
+
disk* — `outputDir` (default: the agent's working directory) as
|
|
171
|
+
`lemonade-<kind>-<timestamp>.<ext>` — and the tool returns the saved path.
|
|
172
|
+
The model relays the path to you. To *view* a generated image in the TUI, ask
|
|
173
|
+
the agent to `read` the PNG afterwards: pi renders images inline in supported
|
|
174
|
+
terminals (Kitty, iTerm2, Ghostty, WezTerm, Warp), and reading it also lets
|
|
175
|
+
the model itself see and verify its own output. The TUI cannot render audio
|
|
176
|
+
or `.glb` meshes at all — those live on disk only.
|
|
177
|
+
|
|
178
|
+
Per tool:
|
|
179
|
+
|
|
180
|
+
| Tool | What it does | Model resolution | Output | Caveats |
|
|
181
|
+
|---|---|---|---|---|
|
|
182
|
+
| `transcribe_audio` | Speech → text | `defaultTranscriptionModel` (Whisper-Large-v3), else first catalog model labeled `transcription`/`whisper` | transcript returned inline (not saved) | the transcription API accepts raw wav only — every other format (mp3, mp4, m4a, webm, flac, ...) is converted to 16 kHz mono wav via **ffmpeg** on pi's machine first. Unloaded Whisper auto-loads into the separate transcription slot |
|
|
183
|
+
| `generate_image` | Text → PNG | arg → `defaultImageModel` → first catalog model labeled `image` | PNG on disk + path | First call may include model load (observed ~12 s). Turbo models want `steps: 4`, `cfg_scale: 1`; the tool schema documents this for the model |
|
|
184
|
+
| `edit_image` | Image + prompt → edited PNG | arg → `defaultImageModel` → first model labeled `edit` (then `image`) | PNG on disk + path | multipart upload of the source PNG; optional `mask_path` (white = edit, black = preserve). Edit-capable models on current build: the Flux family |
|
|
185
|
+
| `vary_image` | Image → variation PNG | arg → `defaultImageModel` → first `edit`/`image` model | PNG on disk + path | no prompt parameter — variation is derived from the input image alone |
|
|
186
|
+
| `upscale_image` | Image → 4x PNG | arg → `defaultUpscaleModel` (RealESRGAN-x4plus) | PNG on disk + path | needs an upscale model pulled on the server; `-anime` variant exists for art. Sends the image as base64 JSON, not multipart |
|
|
187
|
+
| `text_to_speech` | Text → spoken audio | arg → first model labeled `tts` (OpenMOSS-TTS on current builds) | mp3/wav on disk + path | if the TTS model isn't pulled, the request can trigger a slow auto-pull that outlasts the request — pull it via the setup menu first. `voice`/`speed`/`response_format` supported |
|
|
188
|
+
| `generate_audio` | Prompt → music / SFX | arg → first model labeled `music`/`sfx`/`audio` | wav on disk + path | ACE-Step for music (optional `lyrics` with `[verse]`/`[chorus]` tags = sung vocals; omit = instrumental), ThinkSound for SFX. Not downloaded by default — pull first |
|
|
189
|
+
| `classify_text` | Text → ranked label confidences | arg → `defaultClassifierModel` → first catalog model with the `onnxruntime` recipe | inline text readout | encoder classifiers (phishing, PII, prompt-injection...) — millisecond inference in its own classification slot; scores returned highest-first, so the top line is the verdict. Pull classifiers via the setup menu or HF install |
|
|
190
|
+
| `generate_3d_model` | Image → textured `.glb` | arg → first model labeled `3d` (TRELLIS-3D) | .glb on disk + path | minutes per mesh; `resolution` 512/1024/1536; needs TRELLIS pulled. TUI cannot render it — open the file in a 3D viewer |
|
|
191
|
+
|
|
192
|
+
**How `classify_text` handles multiple classifiers and modalities.** Each
|
|
193
|
+
classifier's label universe (phishing/benign, PII/no-PII, 1–5 stars…) is baked
|
|
194
|
+
into the model at training time — the tool is a pipe, not a policy. Three
|
|
195
|
+
layers make multi-classifier use workable: (1) the tool's prompt advertisement
|
|
196
|
+
dynamically enumerates the classifiers actually on the server each session;
|
|
197
|
+
(2) per-call selection via the `model` argument (or `defaultClassifierModel`
|
|
198
|
+
config); (3) runtime self-description — the response's labels reveal the
|
|
199
|
+
chosen model's universe, so a mismatched question ("is this a hot dog?" sent
|
|
200
|
+
to a phishing model) returns an obviously wrong label set and the agent
|
|
201
|
+
retries with the right model. Modality boundaries are architectural:
|
|
202
|
+
`/v1/classify` serves *text encoders only* — image classification routes to
|
|
203
|
+
vision-capable chat models (read the image), and audio classifies only after
|
|
204
|
+
`transcribe_audio`. Sequence-classification models return the flat ranked
|
|
205
|
+
scores; token-classification models (e.g. PII *span* detectors) return
|
|
206
|
+
additional structure, which the tool passes through verbatim.
|
|
207
|
+
|
|
208
|
+
Missing-model behavior is uniform: if no suitable model exists on the server,
|
|
209
|
+
the tool returns a clear error naming what to pull — the agent relays it and
|
|
210
|
+
suggests pulling via `/lemonade-setup`. Never a silent failure.
|
|
211
|
+
|
|
212
|
+
Lemonade also exposes **realtime transcription** over a websocket
|
|
213
|
+
(`ws://<host>:<ws-port>/realtime?model=…`; the port comes from `/health`).
|
|
214
|
+
This extension does not wire that up — it's for live-mic streaming, not file
|
|
215
|
+
transcription.
|
|
216
|
+
|
|
217
|
+
### Agent self-awareness
|
|
218
|
+
|
|
219
|
+
Every chat's system prompt — including virgin chats with no lemonade context —
|
|
220
|
+
carries three guideline lines injected by this extension (via pi's
|
|
221
|
+
`promptGuidelines` mechanism, attached to `transcribe_audio`):
|
|
222
|
+
|
|
223
|
+
- where the machinery lives: `~/.pi/agent/lemonade.json` (base URL, endpoints,
|
|
224
|
+
defaults) and this README + `docs/` (full reference)
|
|
225
|
+
- what to do with it: query the server's HTTP API directly via `bash` +
|
|
226
|
+
`curl` for state questions (loaded models, health, host resources), and
|
|
227
|
+
point the user at `/lemonade-setup` for interactive management
|
|
228
|
+
- which instances exist: the configured fleet, by name and URL, and that
|
|
229
|
+
tools take an optional `server` argument (this line is generated per session)
|
|
230
|
+
|
|
231
|
+
So asking the agent "what models are loaded on your server?" in a fresh chat
|
|
232
|
+
resolves deterministically: read the config, curl `/v1/health`, summarize —
|
|
233
|
+
no hunting for paths, no lucky guesses.
|
|
234
|
+
|
|
235
|
+
### Lemonade status bar (below-editor widget)
|
|
236
|
+
|
|
237
|
+
While a **lemonade model is active**, the extension adds one extra row below
|
|
238
|
+
the editor (pi's `setWidget` mechanism, `belowEditor` placement). It is a
|
|
239
|
+
separate slot from the footer: the built-in footer and any other extension's
|
|
240
|
+
custom footer are **never touched**, and the row disappears the moment a
|
|
241
|
+
non-lemonade model is selected. `session_start` and `model_select` events drive
|
|
242
|
+
the lifecycle; the instance name comes from the active model's `provider`
|
|
243
|
+
field (`lemonade-<instance>` — pi keeps `provider` and `id` separate; the
|
|
244
|
+
`lemonade-<instance>/<model>` form is picker-display only), so switching
|
|
245
|
+
between instances swaps the row onto the new box automatically. If the
|
|
246
|
+
model isn't resolvable at session start, the first turn re-checks.
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
🍋 stackshack · 14.9 tok/s · cache-hit 99% · idle · CPU 2% · GPU 2% · NPU 0% · VRAM 38.8G
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The layout is **fixed** — every segment is present from the first render with
|
|
253
|
+
`…` placeholders until its value lands, and segments never appear or vanish
|
|
254
|
+
afterwards. Narrow terminals truncate the tail (VRAM first).
|
|
255
|
+
|
|
256
|
+
| Segment | Source | Cost |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `🍋 <instance>` | the active model's `provider` field | none |
|
|
259
|
+
| `<n> tok/s` | the session's own `message_start` → `message_end` timing over `usage.output` — always *your* stream, immune to the server's last-client stats | none (event-driven, updates as each message completes) |
|
|
260
|
+
| `cache-hit <pct>%` | the session's own `usage`: pi-ai maps llama.cpp prefix-cache hits onto `usage.cacheRead` (and `usage.input` is the uncached remainder), so the hit rate is `cacheRead / (cacheRead + input)` per request — never another client's numbers | none (per message) |
|
|
261
|
+
| `idle` / `busy` / `busy ·Nq` / `offline` | polled `/v1/health` (`is_busy`/`is_streaming`) + `/metrics` queue depth (`requests_processing` + `requests_deferred`); `offline` in warning color when the box can't be reached | polled every `barPollMs` |
|
|
262
|
+
| `CPU <n>%` · `GPU <n>%` · `NPU <n>%` · `VRAM <n>G` | polled `/v1/system-stats` (`cpu_percent`, `gpu_percent`, `npu_percent`, `vram_gb`). A box whose system-stats reports `npu_percent: null` drops the NPU segment permanently (decided once on first poll — no flicker); an unreachable box keeps the last-known gauge values under the `offline` marker | polled every `barPollMs` |
|
|
263
|
+
|
|
264
|
+
Notes:
|
|
265
|
+
|
|
266
|
+
- **tok/s is the last *completed* message's rate** — it includes TTFT, so it reads slightly conservative vs. llama.cpp's own tps. It does not animate mid-generation (the working spinner covers that).
|
|
267
|
+
- **`barPollMs: 0`** drops the polled segments; the row degrades to instance + tok/s + cache-hit %.
|
|
268
|
+
- **`statusBar: false`** disables the row entirely.
|
|
269
|
+
- A poll in flight when you switch models is discarded — the new instance's row never shows the old box's numbers.
|
|
270
|
+
|
|
271
|
+
### `/lemonade-setup` menu
|
|
272
|
+
|
|
273
|
+
A navigable TUI (↑↓ / enter / esc). On a fresh install (zero instances
|
|
274
|
+
configured), `/lemonade-setup` instead opens a **first-run wizard** —
|
|
275
|
+
*Discover servers* (UDP beacon + HTTP fallback) or *Add instance manually* —
|
|
276
|
+
and the normal menu takes over once the first instance exists.
|
|
277
|
+
|
|
278
|
+
| Menu | What you can do |
|
|
279
|
+
|---|---|
|
|
280
|
+
| **Server status** | The complete state of the box, grouped by the endpoint each metric came from — every server metric flat on one **live** panel that re-fetches every `statusPollMs` (default 2 s) while open, with each snapshot timestamped in the header; a transient fetch failure keeps the last good snapshot. Scrollable (wrap-aware `↑↓/j/k/pgup/pgdn/home/end` scrolling + mouse wheel, `q`/`esc`/`enter` return; position indicator in the footer). With multiple instances configured, `n`/`p` cycles between boxes without leaving the view — the title names the instance shown, and the menu's active instance follows it on exit. Sections: `/v1/health` (loaded models with busy/streaming/backend-health/pid/slot-pool, per-type slot limits, pinned counts, telemetry state), `/v1/system-stats` (CPU/RAM used-total-%, GPU, VRAM, NPU), `/v1/stats` (last-request tok/s, TTFT, in/out tokens, prefix-cache hit rate, lifetime token/request counters, routing decisions), `/v1/system-info` (CPU cores/threads, OS, GPU/NPU inventory incl. NPU power mode and TOPS, model-storage drive usage), and `/metrics` (llama.cpp backend: request queue depth, busy slots per decode, peak sequence length). Each section degrades to an "(unreachable)" line independently — one dead endpoint never blanks the panel |
|
|
281
|
+
| **Server settings** | Discover servers (UDP beacon + HTTP fallback), edit base URL, edit API key, test connection |
|
|
282
|
+
| **Model management** | List catalog (including not-yet-downloaded registry entries), load / unload, pull (download) with live progress + esc-to-cancel, install from Hugging Face (search → pick variant → install as `user.*`), delete, change context size (unload → reload with `ctx_size`, saved), toggle chat-only filter, refresh provider |
|
|
283
|
+
| **Transcription settings** | Default Whisper model, endpoint path, smoke-test a file |
|
|
284
|
+
| **Live server logs** | WebSocket stream of the server's log: full snapshot backlog (up to 5000 entries) then live entries as they happen; Esc to close |
|
|
285
|
+
|
|
286
|
+
Notes on the destructive and slow paths:
|
|
287
|
+
|
|
288
|
+
- **Delete** requires two gates: a confirm dialog, then typing the exact phrase
|
|
289
|
+
`i want to delete <model-id>` — three attempts allowed, each prompt shows
|
|
290
|
+
chances remaining, and Esc aborts immediately.
|
|
291
|
+
- **Change context size** prefills the model's *current* context (from live
|
|
292
|
+
health), accepts `32k`, `1m`, or raw token counts, and warns that the model
|
|
293
|
+
is unloaded and reloaded (the new `ctx_size` is saved for future loads).
|
|
294
|
+
- **Pull** uses lemonade's server-owned download jobs: a live progress view
|
|
295
|
+
(percent, bytes, per-file status, updated every second) with **Esc to cancel**
|
|
296
|
+
(via `/v1/downloads/control`). Blocking pull is kept only as a fallback for
|
|
297
|
+
older servers without job support.
|
|
298
|
+
- **Install from Hugging Face** searches the registry through the server
|
|
299
|
+
(`/v1/registry/search`), lists quantization variants with sizes
|
|
300
|
+
(`/v1/pull/variants`), and installs the chosen one as a `user.*` model —
|
|
301
|
+
including vision/mmproj detection — with the same live progress view.
|
|
302
|
+
|
|
303
|
+
Every change is saved to the config file and re-registers the provider
|
|
304
|
+
immediately — no restart or `/reload` needed.
|
|
305
|
+
|
|
306
|
+
**Server discovery** — lemonade broadcasts a JSON beacon
|
|
307
|
+
(`{"service":"lemonade","hostname":…,"url":…}`) roughly once per second on UDP
|
|
308
|
+
port 13305. The setup menu can listen for it, then falls back to probing common
|
|
309
|
+
ports on localhost *and* the currently configured host.
|
|
310
|
+
|
|
311
|
+
> **WSL2 note:** UDP LAN broadcasts do not propagate into WSL2's NAT'd virtual
|
|
312
|
+
> network, so the beacon may never arrive if pi runs inside WSL2. The HTTP
|
|
313
|
+
> fallback still works, and manual URL entry always works.
|
|
314
|
+
|
|
315
|
+
## Configuration
|
|
316
|
+
|
|
317
|
+
Everything lives in `~/.pi/agent/lemonade.json` — and it is **created for
|
|
318
|
+
you on first run**: if the file is missing, the extension writes a minimal,
|
|
319
|
+
commented blank config (zero servers) instead of blocking, shows an info
|
|
320
|
+
notice pointing at `/lemonade-setup`, and opens a first-run wizard there
|
|
321
|
+
(discovery or manual entry). A config that exists but is unparseable or
|
|
322
|
+
invalid still fails loudly at startup (red error in the chat window, nothing
|
|
323
|
+
registered), because no endpoint is hardcoded and a broken file must not
|
|
324
|
+
target a server that isn't yours.
|
|
325
|
+
|
|
326
|
+
The tracked `lemonade.example.json` in this folder is the canonical
|
|
327
|
+
reference: fully commented, documenting every key and its default, and
|
|
328
|
+
doubly useful as a template:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
cp ~/.pi/agent/extensions/pi-lemonade-link/lemonade.example.json ~/.pi/agent/lemonade.json
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Edit the first `servers` entry's `baseUrl`, trim the rest to taste, restart pi.
|
|
335
|
+
`//` comments are allowed in the config file (the loader strips them);
|
|
336
|
+
note that saving via `/lemonade-setup` rewrites the file and removes
|
|
337
|
+
comments, so keep annotations in your copy of the example. Changes made in
|
|
338
|
+
`/lemonade-setup` apply immediately — no restart or `/reload` needed.
|
|
339
|
+
|
|
340
|
+
A minimal working config is one entry:
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{ "servers": [{ "name": "main", "baseUrl": "http://your-lemonade-server:13305" }] }
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The full shape (values shown are the built-in defaults):
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"servers": [
|
|
351
|
+
{ "name": "main", "baseUrl": "http://localhost:13305",
|
|
352
|
+
"description": "Primary lemonade server." }
|
|
353
|
+
],
|
|
354
|
+
"apiKey": "lemonade",
|
|
355
|
+
"chatPath": "/api/v1",
|
|
356
|
+
"modelsPath": "/v1/models",
|
|
357
|
+
"healthPath": "/v1/health",
|
|
358
|
+
"loadPath": "/v1/load",
|
|
359
|
+
"unloadPath": "/v1/unload",
|
|
360
|
+
"pullPath": "/v1/pull",
|
|
361
|
+
"deletePath": "/v1/delete",
|
|
362
|
+
"downloadsPath": "/v1/downloads",
|
|
363
|
+
"downloadsControlPath": "/v1/downloads/control",
|
|
364
|
+
"registrySearchPath": "/v1/registry/search",
|
|
365
|
+
"pullVariantsPath": "/v1/pull/variants",
|
|
366
|
+
"transcriptionPath": "/v1/audio/transcriptions",
|
|
367
|
+
"imageGenerationPath": "/v1/images/generations",
|
|
368
|
+
"imageEditPath": "/v1/images/edits",
|
|
369
|
+
"imageVariationPath": "/v1/images/variations",
|
|
370
|
+
"imageUpscalePath": "/v1/images/upscale",
|
|
371
|
+
"speechPath": "/v1/audio/speech",
|
|
372
|
+
"audioGenerationPath": "/v1/audio/generations",
|
|
373
|
+
"mesh3dPath": "/v1/3d/generations",
|
|
374
|
+
"classifyPath": "/v1/classify",
|
|
375
|
+
"beaconPort": 13305,
|
|
376
|
+
"chatOnly": true,
|
|
377
|
+
"defaultTranscriptionModel": "Whisper-Large-v3",
|
|
378
|
+
"defaultImageModel": "",
|
|
379
|
+
"defaultUpscaleModel": "RealESRGAN-x4plus",
|
|
380
|
+
"defaultClassifierModel": "",
|
|
381
|
+
"outputDir": "",
|
|
382
|
+
"discoveryTimeoutMs": 5000,
|
|
383
|
+
"beaconTimeoutMs": 3000,
|
|
384
|
+
"loadTimeoutMs": 300000,
|
|
385
|
+
"pullTimeoutMs": 1800000,
|
|
386
|
+
"transcriptionTimeoutMs": 300000,
|
|
387
|
+
"generationTimeoutMs": 600000,
|
|
388
|
+
"statusPollMs": 2000,
|
|
389
|
+
"statusBar": true,
|
|
390
|
+
"barPollMs": 5000
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
| Key | Meaning |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `servers` | **Required** — the only instance store: `[{ "name", "baseUrl", "apiKey"?, "description"? }]`. May be empty on a fresh install (the first `/lemonade-setup` run offers a wizard); the FIRST entry is the default instance (targeted when tools' `server` argument is omitted); reordering changes the default. Each registers as a `lemonade-<name>` provider. |
|
|
397
|
+
| `apiKey` | Shared fallback `Authorization: Bearer …` for instances without their own. A dummy value works for auth-less servers (pi requires non-empty auth to list models). |
|
|
398
|
+
| `*Path` | API endpoint paths, in case a future lemonade changes them. |
|
|
399
|
+
| `chatOnly` | Filter the provider down to chat-capable models. |
|
|
400
|
+
| `defaultTranscriptionModel` / `defaultImageModel` / `defaultUpscaleModel` / `defaultClassifierModel` | Fixed model ids for the tools. Empty = auto-pick from the live catalog (by label, or by `onnxruntime` recipe for classifiers). |
|
|
401
|
+
| `outputDir` | Where generated files (images, audio, meshes) are saved. Empty = agent's working directory. |
|
|
402
|
+
| `*TimeoutMs` | Per-operation timeouts. `pullTimeoutMs` only caps the *blocking* pull fallback — normal pulls use download jobs with live progress. |
|
|
403
|
+
| `statusPollMs` | How often the setup-menu status panel re-fetches while open (live refresh). `0` = static snapshot. |
|
|
404
|
+
| `statusBar` | Show the below-editor lemonade status bar while a lemonade model is active (see *Lemonade status bar*). |
|
|
405
|
+
| `barPollMs` | Server-poll cadence for the status bar's busy/queue + CPU/GPU/NPU/VRAM segments. `0` = local metrics only (instance, tok/s, cache-hit %). |
|
|
406
|
+
|
|
407
|
+
## Offline & unreachable networks
|
|
408
|
+
|
|
409
|
+
When pi starts on a network with no route back to the lemonade LAN (different
|
|
410
|
+
WAN, VPN down, box asleep):
|
|
411
|
+
|
|
412
|
+
- **Startup never blocks longer than `discoveryTimeoutMs`** (~5 s by default —
|
|
413
|
+
lower it in config if you work offline often).
|
|
414
|
+
- **Only the lemonade provider is affected** — every other provider in
|
|
415
|
+
`/model` (built-ins, other extensions) is untouched.
|
|
416
|
+
- **No lemonade listings when unreachable.** If an instance can't be reached
|
|
417
|
+
at startup or refresh, it contributes nothing to `/model` — an unreachable
|
|
418
|
+
model is not listable. This is fully automated in both directions: entries
|
|
419
|
+
appear exactly when the box is reachable and disappear when it isn't
|
|
420
|
+
(until the next startup or Refresh re-checks).
|
|
421
|
+
**The accepted consequence:** sessions or scripts pinned to
|
|
422
|
+
`lemonade-<name>/<model>` hard-fail model resolution while offline
|
|
423
|
+
(`Error: Model "lemonade/<id>" not found`), and resume working once the box
|
|
424
|
+
is back and a Refresh has re-registered it. Non-reachable chat requests are
|
|
425
|
+
the honest outcome; phantom listings are never shown.
|
|
426
|
+
- **Tools fail with actionable text, not cryptic errors**: every tool detects
|
|
427
|
+
the unreachable server and returns the same message — *where* it tried to
|
|
428
|
+
connect and *what to do about it* (reconnect, or point the extension at a
|
|
429
|
+
reachable instance via `/lemonade-setup`). No misleading "pull a model
|
|
430
|
+
first" advice when the real problem is the network, and no bare
|
|
431
|
+
`fetch failed`.
|
|
432
|
+
- **The setup menu diagnoses it**: Server status shows an explicit
|
|
433
|
+
`Unreachable: <health URL>` panel, and Test connection reports the failure.
|
|
434
|
+
- **The agent can self-diagnose**: the self-awareness guidelines point it at
|
|
435
|
+
the config, so asking the agent "are you connected to lemonade?" resolves
|
|
436
|
+
via a direct probe rather than guessing.
|
|
437
|
+
|
|
438
|
+
**Levels of availability checking** (each with one job, each defined once):
|
|
439
|
+
|
|
440
|
+
| Level | When | What it does | Where the logic lives |
|
|
441
|
+
|---|---|---|---|
|
|
442
|
+
| Registration probe | every startup / refresh | decides what goes into the provider: the live catalog, or nothing at all when unreachable — no phantom listings | `registerInstanceProvider` |
|
|
443
|
+
| Tool fetch | every tool call | converts network rejections into the actionable unreachable message; user-cancels pass through | `toolFetch()` — used by all nine tools |
|
|
444
|
+
| Auto-pick | model resolution | distinguishes unreachable (`null`) from no-match (`undefined`) so advice fits the situation | `pickModelByLabels` / `pickModelByRecipe` |
|
|
445
|
+
| Instance resolution | every tool call with a `server` arg | resolves the named box's config view or returns an error listing known instances | `instanceView()` |
|
|
446
|
+
|
|
447
|
+
Tools stay deliberately stateless — reachability is a per-call fact (networks
|
|
448
|
+
change mid-session), so no connection state is cached between calls. The only
|
|
449
|
+
repetition is the three-line binding guard at the top of each tool, which is
|
|
450
|
+
what makes the rest of each tool body instance- and failure-aware for free.
|
|
451
|
+
|
|
452
|
+
## Multiple lemonade instances
|
|
453
|
+
|
|
454
|
+
The extension supports **simultaneous multi-instance use**. Each configured
|
|
455
|
+
lemonade box becomes its own pi provider, every tool can target any instance,
|
|
456
|
+
and the setup menu operates per-instance.
|
|
457
|
+
|
|
458
|
+
**Configuration** — there is no separate default instance: `servers[]` is
|
|
459
|
+
the *only* instance store, and the first entry IS the default. Every entry
|
|
460
|
+
has a **name** (required) and an optional **description** (free-form context
|
|
461
|
+
for your own reminder's sake). The shared top-level `apiKey` applies to any
|
|
462
|
+
entry that doesn't define its own:
|
|
463
|
+
|
|
464
|
+
```json
|
|
465
|
+
{
|
|
466
|
+
"servers": [
|
|
467
|
+
{ "name": "main", "baseUrl": "http://192.168.1.10:13305",
|
|
468
|
+
"description": "Primary lemonade server." },
|
|
469
|
+
{ "name": "second-server", "baseUrl": "http://192.168.1.20:13305",
|
|
470
|
+
"description": "The other box, mostly for experiments." }
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Names identify instances everywhere, without prejudice: provider ids, model
|
|
476
|
+
addresses, and picker brackets are uniformly `lemonade-<name>` for EVERY
|
|
477
|
+
instance — the default reads `[lemonade-main]` exactly like an extra
|
|
478
|
+
reads `[lemonade-second-server]`. Descriptions are free-form context shown in the
|
|
479
|
+
instance lists; edit both via *Manage instances → Edit instance*. Renaming
|
|
480
|
+
any instance changes its provider id (and re-registers it), so pinned
|
|
481
|
+
sessions and `--model lemonade-<old-name>/...` references must follow the
|
|
482
|
+
rename — the cost of a perfectly uniform namespace.
|
|
483
|
+
|
|
484
|
+
**How instances are exposed:**
|
|
485
|
+
|
|
486
|
+
- **Providers:** every instance registers as `lemonade-<name>` — the default
|
|
487
|
+
included, no special case. Picker brackets, model addresses, and provider
|
|
488
|
+
column all read `lemonade-<name>` uniformly. Each is independently annotated `loaded` / `on-demand`
|
|
489
|
+
from that box's own health state. Unreachable boxes register nothing —
|
|
490
|
+
there are no offline placeholders for any instance.
|
|
491
|
+
- **Tools:** every tool takes an optional `server` argument naming an
|
|
492
|
+
instance. Omitted → default. Unknown names return an error listing the
|
|
493
|
+
available instances — never a wrong-box dial. Model auto-pick, status, and
|
|
494
|
+
everything else resolve against the selected instance's catalog.
|
|
495
|
+
- **Self-awareness:** the prompt guideline enumerates the configured
|
|
496
|
+
instances by name and URL every session, so virgin chats know the fleet
|
|
497
|
+
without hunting.
|
|
498
|
+
- **Setup menu:** a *Switch instance* item (appears when extras exist) picks
|
|
499
|
+
which box Status, Model management, and Live logs act on — and the status
|
|
500
|
+
panel itself cycles instances in-place with `n`/`p`, syncing the menu's
|
|
501
|
+
active instance on exit; Server settings → *Manage instances* lists (with
|
|
502
|
+
live reachability), adds, and removes instances; discovered servers can be
|
|
503
|
+
registered as default or as a named instance.
|
|
504
|
+
|
|
505
|
+
**The one-place rule:** instance semantics are defined exactly once —
|
|
506
|
+
`instanceView()` returns a *config view* (same settings, swapped
|
|
507
|
+
baseUrl/apiKey) that flows through every existing helper unchanged, so tools,
|
|
508
|
+
discovery, TUI actions, and error handling all inherit instance behavior
|
|
509
|
+
without per-tool logic.
|
|
510
|
+
|
|
511
|
+
## Reference documentation (API ground truth)
|
|
512
|
+
|
|
513
|
+
This folder's [`docs/`](./docs) directory contains the complete official
|
|
514
|
+
Lemonade endpoint specification, snapshotted from the upstream repo
|
|
515
|
+
(`lemonade-sdk/lemonade`, `docs/api/`) on 2026-09-12 — the same markdown the
|
|
516
|
+
[docs site](https://lemonade-server.ai/docs/api/) renders. It is the source of
|
|
517
|
+
ground truth for every endpoint, parameter, and response shape this extension
|
|
518
|
+
uses:
|
|
519
|
+
|
|
520
|
+
| File | Contents |
|
|
521
|
+
|---|---|
|
|
522
|
+
| [`docs/README.md`](./docs/README.md) | Spec index and design philosophy |
|
|
523
|
+
| [`docs/openai.md`](./docs/openai.md) | OpenAI-compatible surface: chat/completions, embeddings, audio (transcription, speech, generations), images (generation/edit/variation/upscale), realtime, `/v1/models` + labels taxonomy |
|
|
524
|
+
| [`docs/lemonade.md`](./docs/lemonade.md) | Lemonade-specific surface: load/unload/pull/delete, download jobs + control, registry search + pull variants, health/stats/system-stats/system-info, classify, audio/3D generation, log streaming, job engine, install/uninstall |
|
|
525
|
+
| [`docs/mcp.md`](./docs/mcp.md) | The MCP gateway (lemonade as an MCP server) |
|
|
526
|
+
| [`docs/ollama.md`](./docs/ollama.md), [`docs/anthropic.md`](./docs/anthropic.md), [`docs/llamacpp.md`](./docs/llamacpp.md) | Other compatibility surfaces (rerank, slots, etc.) |
|
|
527
|
+
|
|
528
|
+
When lemonade updates, re-sync with:
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
git clone --depth 1 --filter=blob:none --sparse https://github.com/lemonade-sdk/lemonade /tmp/lemonade \
|
|
532
|
+
&& cd /tmp/lemonade && git sparse-checkout set docs/api \
|
|
533
|
+
&& cp docs/api/*.md ~/.pi/agent/extensions/pi-lemonade-link/docs/
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## Lemonade behavior reference
|
|
537
|
+
|
|
538
|
+
Empirically confirmed against lemonade 11.7.0 (cross-checked with the
|
|
539
|
+
[official endpoint spec](https://lemonade-server.ai/docs/api/)):
|
|
540
|
+
|
|
541
|
+
- **Auto-loading.** A request for an unloaded model triggers an automatic load
|
|
542
|
+
attempt. If the model's slot is free or holds an unpinned model, lemonade
|
|
543
|
+
evicts/loads and serves the request. If the slot is occupied by a *pinned*
|
|
544
|
+
model, you get `slots_pinned_error: "All loaded models of type … are pinned.
|
|
545
|
+
Unload a model first."` — unload or unpin via the setup menu, then retry.
|
|
546
|
+
- **Per-type slots.** The server holds one slot per model type (llm,
|
|
547
|
+
transcription, image, embedding, …), so Whisper can be loaded alongside your
|
|
548
|
+
chat model.
|
|
549
|
+
- **change-ctx.** Implemented as unload → reload with `ctx_size` +
|
|
550
|
+
`save_options: true`, which persists the choice for future loads.
|
|
551
|
+
- **Status panel sources.** All five metric endpoints are live-confirmed:
|
|
552
|
+
`/v1/health` (incl. per-model `is_busy`/`is_streaming`/`backend_health`/
|
|
553
|
+
`slot_pool`/`residency_class`/`last_use`/`pid` and `pinned_models` counters),
|
|
554
|
+
`/v1/system-stats`, `/v1/stats` (cumulative `*_total` counters + per-request
|
|
555
|
+
prefix-cache hits via `cache_tokens`), `/v1/system-info` (hardware inventory
|
|
556
|
+
+ `model_storage` drive usage), and root-level `/metrics` (Prometheus text;
|
|
557
|
+
`lemonade_llamacpp_*` backend gauges incl. `requests_processing`/
|
|
558
|
+
`requests_deferred` queue depth and `n_tokens_max` peak sequence length).
|
|
559
|
+
Caveats seen in the field: the Linux `amd_gpu` device `name` can come back as
|
|
560
|
+
a raw device id (the panel falls back to `family`), and `/metrics` requires
|
|
561
|
+
the bearer key when `LEMONADE_API_KEY` is set. Power draw in watts is *not*
|
|
562
|
+
exposed by any lemonade endpoint — utilization only (`*_percent`).
|
|
563
|
+
|
|
564
|
+
## Troubleshooting
|
|
565
|
+
|
|
566
|
+
| Symptom | Fix |
|
|
567
|
+
|---|---|
|
|
568
|
+
| No `lemonade-<name>/*` models in `/model` | `/lemonade-setup` → Server settings → Test connection; check `baseUrl`; run Refresh |
|
|
569
|
+
| Error: `slots_pinned_error` on first message | The pinned model occupies the slot — unload it (Model management), or select the loaded model |
|
|
570
|
+
| `transcribe_audio` "not available" from the agent | Tools need a `promptSnippet` to appear in the system prompt — that's pi behavior, not a bug here; this extension sets it |
|
|
571
|
+
| Transcription of `.mp4` fails with ffmpeg missing | Install ffmpeg on the machine running pi (container formats are converted locally before upload) |
|
|
572
|
+
| Discovery finds nothing | Expected inside WSL2 (UDP broadcasts don't cross the NAT); set the URL manually |
|
|
573
|
+
| Server unreachable at startup | The instance registers nothing — no lemonade models in `/model` until it's reachable; fix the URL and Refresh |
|
|
574
|
+
| Pull seems to hang | It's downloading — the blocking pull path sends no progress. Large models take minutes; the `pullTimeoutMs` cap guarantees control returns |
|
|
575
|
+
| Status panel shows "(unreachable)" for some sections | That one endpoint failed for this box — `/metrics` in particular requires the API key when the server sets `LEMONADE_API_KEY`. The rest of the panel stays live |
|
|
576
|
+
| Status panel never refreshes | `statusPollMs` is `0` in the config — that disables live refresh. Set it to a poll interval in ms (default 2000) |
|
|
577
|
+
|
|
578
|
+
## MCP Gateway (lemonade as an MCP server)
|
|
579
|
+
|
|
580
|
+
Lemonade exposes itself as a genuine MCP server at `POST /mcp`
|
|
581
|
+
(Streamable HTTP), serving five tools: `lemonade_list_models`, `lemonade_chat`,
|
|
582
|
+
`lemonade_transcribe_audio`, `lemonade_generate_image`, and `lemonade_omni`.
|
|
583
|
+
|
|
584
|
+
pi's MCP manager only accepts *remote* MCP endpoints over HTTPS, so this
|
|
585
|
+
setup ships a tiny local bridge instead:
|
|
586
|
+
|
|
587
|
+
- `~/.pi/agent/bin/lemonade-mcp-proxy.mjs` — stdio→HTTP proxy: reads
|
|
588
|
+
newline-delimited JSON-RPC on stdin, forwards each message to the lemonade
|
|
589
|
+
gateway, writes responses to stdout (SSE frames unwrapped, session id
|
|
590
|
+
passed through).
|
|
591
|
+
- `~/.pi/agent/mcps-local/lemonade/server.json` — MCP manager entry
|
|
592
|
+
(transport: stdio, connection: lazy, **disabled by default**).
|
|
593
|
+
|
|
594
|
+
To use it: open `/mcp` in pi, pick **lemonade**, choose *Test or refresh*,
|
|
595
|
+
inspect the tool manifest, select the tools you want, and enable the server.
|
|
596
|
+
The first approved call starts the connection. Note the overlap: these MCP
|
|
597
|
+
tools duplicate what this extension registers natively — enable them only if
|
|
598
|
+
you want lemonade tools available in *other* MCP-consuming setups.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Lemonade Endpoints Spec
|
|
2
|
+
|
|
3
|
+
The Lemonade HTTP service provides a wide array of standards-compliant and custom endpoints.
|
|
4
|
+
|
|
5
|
+
Our design philosophy is:
|
|
6
|
+
|
|
7
|
+
1. Ensure that Lemonade works out-of-box with all popular local AI apps.
|
|
8
|
+
2. Prioritize using a pre-existing standard for functionality whenever possible.
|
|
9
|
+
3. Add sufficient custom functionality to enable developers to build highly polished experiences.
|
|
10
|
+
|
|
11
|
+
This spec details all supported endpoints. It is organized into pages that correspond to which organization (OpenAI, Ollama, Lemonade, etc.) defined the endpoints.
|
|
12
|
+
|
|
13
|
+
| API | Description |
|
|
14
|
+
|-----|-------------|
|
|
15
|
+
| [OpenAI-Compatible API](./openai.md) | Start here for the main API surface used by most SDKs and clients. |
|
|
16
|
+
| [Ollama-Compatible API](./ollama.md) | Use this if your client expects Ollama-style behavior and routes. |
|
|
17
|
+
| [Anthropic-Compatible API](./anthropic.md) | Use this for clients built around Anthropic's message format. |
|
|
18
|
+
| [MCP Gateway](./mcp.md) | Use this to expose Lemonade as a Model Context Protocol server (POST /mcp). |
|
|
19
|
+
| [llama.cpp-Specific API](./llamacpp.md) | Reference for llama.cpp-specific compatibility and conventions. |
|
|
20
|
+
| [Lemonade-Specific API](./lemonade.md) | Local-first API for managing lifecycle, configuration, backends, etc. |
|
|
21
|
+
|
|
22
|
+
A running server also serves these pages itself: [`GET /v1/docs`](./lemonade.md#get-v1docs) lists what it has and [`GET /v1/docs/{page}`](./lemonade.md#get-v1docspage) returns one, so the reference always matches the installed version and stays available offline.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Anthropic-Compatible API
|
|
2
|
+
|
|
3
|
+
Lemonade supports an initial Anthropic Messages compatibility endpoint for applications that call Claude-style APIs.
|
|
4
|
+
|
|
5
|
+
| Endpoint | Status | Notes |
|
|
6
|
+
|----------|--------|-------|
|
|
7
|
+
| `POST /v1/messages` | Supported | Supports both streaming and non-streaming. Query params like `?beta=true` are accepted. |
|
|
8
|
+
|
|
9
|
+
Current scope focuses on message generation parity for common fields (`model`, `messages`, `system`, `max_tokens`, `temperature`, `stream`, and basic `tools`). Unsupported or unimplemented Anthropic-specific fields are ignored and surfaced via warning logs/headers.
|
|
10
|
+
|
|
11
|
+
Those limits apply only where Lemonade converts to and from the OpenAI shape. A cloud provider registered with `--wire-format anthropic` is relayed unconverted, so no field is dropped — see [Cloud Offload](../guide/configuration/cloud.md#providers-that-speak-the-anthropic-messages-format).
|