pi-freeflow 1.40.1 → 1.41.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/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.41.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add seven new free models — all keyless — for 37 in total (12 OpenCode Zen + 16 KiloCode Gateway + 1 Cline + 8 OVHcloud AI Endpoints).
8
+
9
+ New on OpenCode Zen:
10
+
11
+ - `minimax-m3.1-flash-free` (MiniMax M3.1 Flash): 1M context, 524,288 output, vision. Effort `low` through `max`.
12
+ - `flint-40b-free` (Flint 40B): 262,144 context, 65,536 output, text-only. Effort `low` through `max`. Availability is bursty — an upstream 503 burst means retry, not that the model is gone.
13
+
14
+ New on KiloCode Gateway (OpenRouter-style reasoning, keyless):
15
+
16
+ - `minimax/minimax-m3.1-flash:free` (MiniMax M3.1 Flash): 1M context, 524,288 output, vision. Reasoning is mandatory upstream and there are no reasoning deltas on tool turns.
17
+ - `nvidia/nemotron-3.5-super-vl:free` (Nemotron 3.5 Super VL): 1M context, 131,072 output, vision. NVIDIA's trial terms allow training on prompts, and `max` effort can draw a transient 502/503 worth retrying.
18
+ - `reflection/beam-free` (Beam 1.0), alias `beam-kilo`: 1,048,576 context, 131,072 output, text-only. Reasoning is always on.
19
+ - `recursive/flint-v1.0:free` (Flint 40B), alias `flint-kilo`: 262,144 context, 65,536 output, text-only. Thinking is mandatory and can consume a tight output budget.
20
+ - `xingchen-agi/xing4.0-29b-a4b:free` (Xing4.0 29B A4B), alias `xing4.0`: 262,144 context, 235,929 output, text-only. Expect the shared upstream 429 quota often; that is a rate limit, not a dead model.
21
+
22
+ Short names: `minimax-m3.1-flash` and `flint` stay on the Zen lane. The Kilo twins answer to `flint-kilo` and `beam-kilo`, while `beam` and `nemotron-3.5-super-vl` keep pointing at their existing Zen entries, so no saved selection changes lane.
23
+
24
+ ### Patch Changes
25
+
26
+ - Protocol cleanup around reasoning effort, failover limits and tool definitions.
27
+
28
+ Three small drifts between what a lane accepted and what it advertised are settled. Unsupported reasoning-effort knobs are now clamped on the Chat Completions path exactly as the Responses path already clamped them, so a too-high or unsupported level keeps the turn on the same model instead of walking into an upstream refusal. Failover bodies headed to Kilo now clamp `max_completion_tokens` alongside `max_tokens`, saving a wasted round trip. The user-facing tool definitions now carry `strict` in the position the Chat Completions shape canonically uses, with both positions still accepted when reading a caller's tools back.
29
+
30
+ The Anthropic-shaped messages lane no longer deletes Anthropic-native forced tool choices (`any` or a named tool) — it was documented to keep the caller's own choice semantics and now actually does; the chat and Responses lanes still normalize choices to `auto`-or-absent as upstream requires.
31
+ - Two OVH-hosted models no longer lose their answer to an upstream quirk, and an empty upstream stream now reports itself instead of looking like a blank reply.
32
+
33
+ Qwen3.8 27B on OVH answers a request carrying a thinking-effort knob with an empty stream; the proxy now drops that knob for this one model, so the turn produces the model's own reasoning and answer as usual. Mistral Small 3.2 24B requires tool-call ids of exactly nine letters and digits and discards the whole turn otherwise, so ids replayed from your session history are rewritten to that shape — consistently, so an assistant call and the tool result that answers it still match.
34
+
35
+ When OVH does return nothing at all, a non-streaming request now answers with a clear `502` and "upstream returned an empty stream" instead of a fabricated empty completion.
36
+ - Retire Cline's `cline-free/muse-spark-1.3-contributor` from the catalog.
37
+
38
+ Cline stopped resolving the id: every request answers HTTP 404 `model not found` with the hint `Cline has no such model`, while the sibling Cline model still resolves on the same account. The picker no longer offers Muse Spark 1.3. The id stays on the retired list, so a stale local cache or a refreshed upstream listing cannot bring it back.
39
+
40
+ The Cline model that remains (`z-ai/glm-5.3-flash`) still works once the signed-in account has free credit. Clients that already saved the retired model simply stop seeing it.
41
+ - Retire `exo-free` from the catalog.
42
+
43
+ The free endpoint answers HTTP 410 "Model exo-free has been deprecated" on every request path and the model is no longer on the service's model list, so the picker no longer offers it. The id stays on the retired list, which means a stale local cache or a refreshed upstream listing cannot put it back.
44
+ - Parallel tool calls streamed through the proxy no longer corrupt each other.
45
+
46
+ Several free models label every parallel tool call with the same index while still giving each call its own id. On streamed turns the proxy merged calls by index alone, so the argument fragments of two parallel calls interleaved into one packet: one call could vanish entirely, or the arguments came through as glued-alongside JSON the host had to reject (which looked like the model failing at tool calling and the turn being retried). Streamed tool calls are now tracked the same way the assembled (non-streamed) ones always were — per call id, falling back to the wire index only when a call carries none — and a call whose index collides with another is renumbered so the host still sees two well-formed, re-executable calls. Streams with a single tool call are byte-for-byte unchanged.
47
+ - Streamed tool calls now reach the client with the tool name exactly once.
48
+
49
+ One upstream (OpenCode Zen) streams a single tool call as two frames that BOTH carry the complete name: a name-only frame, then a frame repeating that name together with the arguments. The proxy appended every name it saw, so the assembled call came out named `get_weatherget_weather` — a name no tool declaration matches. Streamed hosts bound the call by index and happened to tolerate it; anything reading the non-streamed aggregate, and any host that trusts the assembled packet, got a call it could not execute. A name that arrives alongside arguments is now treated as the authoritative whole name and replaces what was accumulated, while name-only deltas keep appending so ordinary piecewise-streamed names still reassemble. Both the streamed path and the assembled path apply the same rule.
50
+ - Failed and retried tool-call turns are now recorded locally so they can be diagnosed after the fact.
51
+
52
+ Every streamed turn that declared tools — and every non-streamed Chat Completions turn — leaves one line in `<data dir>/toolcall-audit.jsonl` describing how it ended: the caller's declared tool names, the calls the model actually made (name, argument size, whether the arguments parsed, whether the name was one the caller declared), the finish reason, and the classified failure. A turn where the model answered with plain text instead of calling a tool, or called a name the host never declared, now says so instead of vanishing into a generic red error. A Responses or Messages body served without an event stream is recorded as unclassifiable rather than counted against the model.
53
+
54
+ Each upstream attempt abandoned in favour of another is recorded with its reason — rate limit, gateway timeout, payload limit, relay auth rejection, header timeout, socket fault — and, when the host re-sends the same conversation within ten minutes, the new turn carries the earlier one's request id and failure, so a failed first attempt and its host retry are attributable to one conversation.
55
+
56
+ The log records names, sizes, booleans and reasons only: message text, tool schemas and tool-argument values are never written. It is best-effort, never affects a request, stays empty for turns that declared no tools, and rotates to `.1` at 5 MiB. Two `jq` one-liners in the architecture docs list failures by model and retried conversations.
57
+
3
58
  ## 1.40.1
4
59
 
5
60
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-freeflow
2
2
 
3
- > 32 free models with up to 1M context. No API keys to manage. Add your own relays to spread requests across more IPs.
3
+ > 37 free models with up to 1M context. No API keys to manage. Add your own relays to spread requests across more IPs.
4
4
 
5
5
  Thin by design: a model list, a relay proxy, and a log. The host (`pi-ai`) handles thinking, normalization, and provider behavior.
6
6
 
@@ -16,12 +16,13 @@ Thin by design: a model list, a relay proxy, and a log. The host (`pi-ai`) handl
16
16
 
17
17
  | Feature | What it does | Cost |
18
18
  | :--- | :--- | :--- |
19
- | **32 free models** | 11 from OpenCode Zen, 11 from KiloCode Gateway, 2 from Cline, 8 from OVHcloud AI Endpoints, context windows up to 1M. Full list below. | **$0** |
19
+ | **37 free models** | 12 from OpenCode Zen, 16 from KiloCode Gateway, 1 from Cline, 8 from OVHcloud AI Endpoints, context windows up to 1M. Full list below. | **$0** |
20
20
  | **Relay pool** | Route requests through your own relays: a self-hosted egress pool (many egress IPs on one small host) or Cloudflare Workers / Deno Deploy / Vercel Edge workers. Traffic spreads across the pool, or stays on one sticky relay. A relay that rate-limits, times out, or drops the connection cools down while healthy ones take its traffic. | **$0** beyond your host or platforms' free tiers |
21
21
  | **Automatic fallback** | When every relay is cooling down, requests go direct to upstream instead of failing. | **$0** |
22
- | **Short model names** | Most models have a slash-free, colon-free alias, plus an optional `:effort` suffix for thinking depth. You type `freeflow/<name>`; the two Cline models have no alias and must be typed in full. | **$0** |
22
+ | **Short model names** | Most models have a slash-free, colon-free alias, plus an optional `:effort` suffix for thinking depth. You type `freeflow/<name>`; the Cline model has no alias and must be typed in full. | **$0** |
23
23
  | **Shared local proxy** | One daemon on `127.0.0.1:28180` serves every session on the machine, so parallel subagents reuse it instead of opening their own connections. Loopback-only and token-gated: hosts send the token automatically, external tools read `~/.pi/agent/token`. | **$0** |
24
24
  | **Logs you can read** | `~/.pi/agent/pi-freeflow.log`, rotated at 10MB. Tail it with `/freeflow logs`. | **$0** |
25
+ | **Tool-call diagnostics** | `~/.pi/agent/toolcall-audit.jsonl`, rotated at 5MB. One line per streamed turn that declared tools (and per non-streamed Chat Completions turn) — the calls it made, argument size and validity, finish reason, classified failure — plus one line per abandoned upstream attempt. Names, sizes and reasons only. | **$0** |
25
26
 
26
27
  ---
27
28
 
@@ -188,21 +189,21 @@ The same command set works identically in OMP and Pi:
188
189
  /freeflow use <url|index|label> # Switch active relay
189
190
  /freeflow url <url> # Set the active relay URL directly
190
191
  /freeflow add <url> [label] # Add new relay to the pool
191
- /freeflow label <index|url> <name># Assign a friendly label to a relay
192
+ /freeflow label|rename <index|url> <name> # Assign a friendly label to a relay
192
193
  /freeflow remove <index|url|label># Remove a relay from the pool
193
194
  /freeflow test <index|url|label> # Probe a relay for reachability and latency
194
195
  /freeflow on|off|auto|sticky|spread # Relay mode (auto/sticky = sticky primary, spread = rotate pool)
195
196
  /freeflow deploy <platform> # Guided relay deploy: vercel|cloudflare|deno, token in-memory, auto-adds
196
- /freeflow logs [lines] # Inspect recent proxy logs
197
+ /freeflow logs [lines] # Inspect recent proxy logs (alias: log)
197
198
  /freeflow trace [req-id] # Tail logs filtered by request correlation ID
198
199
  /freeflow refresh # Reload the model catalog from live upstreams
199
200
  /freeflow reload models # Alias of refresh
200
201
  /freeflow update # Check for and install a package update
201
- /freeflow autoupdate [on|off] # View or toggle automatic background updates
202
+ /freeflow autoupdate [on|off] # View or toggle automatic background updates (also: update auto on|off)
202
203
  /freeflow install-startup # Install the login-startup hook so the daemon comes up with your session
203
204
  /freeflow uninstall-startup # Remove the login-startup hook
204
205
  /freeflow debug on | off # Toggle full HTTP lifecycle debug logging
205
- /freeflow kill # Stop the shared proxy daemon now (restarts on next use)
206
+ /freeflow kill # Stop the shared proxy daemon now (restarts on next use; aliases: stop, shutdown)
206
207
  /freeflow cline login # Sign in through the browser, save to the per-user pool
207
208
  /freeflow cline accounts # List saved Cline logins
208
209
  /freeflow cline logout # Remove a saved Cline login
@@ -213,17 +214,17 @@ The same command set works identically in OMP and Pi:
213
214
 
214
215
  ---
215
216
 
216
- ### 32 models, one command
217
+ ### 37 models, one command
217
218
 
218
219
  ```bash
219
220
  /model → freeflow → pick
220
221
  ```
221
222
 
222
- Every model specification is reconciled against [models.dev](https://models.dev/), [OpenRouter](https://openrouter.ai/), and OMP's built-in catalog on 2026-10-20: a free and a non-free variant of the same model share the highest published context window, output ceiling, and input modalities across its twins. The catalog follows that published spec even where a free lane enforces a lower runtime cap; the authentic upstream error is surfaced above it. (Measured runtime caps: MiMo ~348K, GLM 5.3 Flash ~297K, Laguna S 262K.) Lane-published hard caps stay authoritative where the lane refuses above them: OVH's advertised context windows, the 1,000,000-token Nemotron 3 Ultra ceiling, and Dots3-Note's 393,216 served window (its only provider refused a 460,800 output budget on 2026-10-20).
223
+ Every model specification is reconciled against [models.dev](https://models.dev/), [OpenRouter](https://openrouter.ai/), and OMP's built-in catalog on 2026-10-22: a free and a non-free variant of the same model share the highest published context window, output ceiling, and input modalities across its twins. The catalog follows that published spec even where a free lane enforces a lower runtime cap; the authentic upstream error is surfaced above it. Where the lane itself refuses above a published figure, the served boundary wins (MiniMax M3.1 Flash: published 512,000, served 524,288). (Measured runtime caps: MiMo ~348K, GLM 5.3 Flash ~297K, Laguna S 262K.) Lane-published hard caps stay authoritative where the lane refuses above them: OVH's advertised context windows, the 1,000,000-token Nemotron 3 Ultra ceiling, and Dots3-Note's 393,216 served window (its only provider refused a 460,800 output budget on 2026-10-20).
223
224
 
224
225
  The host catalog carries text and image inputs only. Upstream video and audio support (MiMo: audio + video; GLM 5.3 Flash: video + PDF; Nemotron Super VL and Nano Omni: video) is therefore not selectable from the picker and is not declared in `input`.
225
226
 
226
- #### OpenCode Zen (11 models), Chat Completions API
227
+ #### OpenCode Zen (12 models), Chat Completions API
227
228
 
228
229
  Good defaults for long coding sessions and agentic work.
229
230
 
@@ -238,19 +239,20 @@ Good defaults for long coding sessions and agentic work.
238
239
  | `longcat-2.5-preview-free` | Meituan LongCat | **1M** (1.048.576) | **131K** (131.072) | `minimal … xhigh` | ✅ |
239
240
  | `glm-5.3-flash-free` | Zhipu AI | **1M** (1.048.576) | **131K** (131.072) | `low / high / max` | ✅ |
240
241
  | `beam-free` | Reflection AI | **1M** (1.048.576) | **131K** (131.072) | `low … max` | ❌ |
241
- | `exo-free` | Undisclosed | **1M** (1.048.576) | **131K** (131.072) | `minimal … xhigh` | ✅ |
242
242
  | `deepseek-v4.1-flash-free` | DeepSeek | **1M** (1.048.576) | **393K** (393.216) | `low / high / max` | ✅ |
243
+ | `minimax-m3.1-flash-free` | MiniMax | **1M** (1.000.000) | **512K** (524.288) | `low … max` | ✅ |
244
+ | `flint-40b-free` | Recursive | **262K** (262.144) | **65K** (65.536) | `low … max` | ❌ |
243
245
 
244
- #### KiloCode Gateway (11 models), OpenRouter compatible
246
+ #### KiloCode Gateway (16 models), OpenRouter compatible
245
247
 
246
- Keyless access. Short aliases work for every row (the full ID is in parentheses).
248
+ Keyless access. Most rows have a short alias (shown with the full ID in parentheses); a row that shows only the full ID has no short form.
247
249
 
248
250
  | Model ID | Creator / Lab | Context | Max Output | Thinking | Vision |
249
251
  | :--- | :--- | :--- | :--- | :--- | :--- |
250
252
  | `dots-3-note-preview` (`dots-studio/...:free`) | Dots Studio | **384K** (393.216) | **256K** (262.144) | `minimal…xhigh`\* | ✅ |
251
253
  | `nemotron-3-nano-omni` (`nvidia/...:free`) | NVIDIA | **262K** (262.144) | **65K** (65.536) | `minimal…xhigh`\* | ✅ |
252
254
  | `nemotron-3-ultra-550b` (`nvidia/...:free`) | NVIDIA | **1M** (1.000.000) | **182K** (182.520) | `minimal…xhigh`\* | ❌ |
253
- | `nvidia/nemotron-3.5-lightning:free` | NVIDIA | **1M** (1.000.000) | **235K** (235.929) | `minimal…xhigh`\* | ❌ |
255
+ | `nemotron-3.5-lightning` (`nvidia/nemotron-3.5-lightning:free`) | NVIDIA | **1M** (1.000.000) | **235K** (235.929) | `minimal…xhigh`\* | ❌ |
254
256
  | `north-mini-code` (`cohere/...:free`) | Cohere | **256K** (256.000) | **64K** (64.000) | `minimal…xhigh`\* | ❌ |
255
257
  | `laguna-s-2.1:free` (`poolside/...:free`) | Poolside | **1M** (1.048.576) | **131K** (131.072) | `minimal…xhigh`\* | ❌ |
256
258
  | `laguna-xs-2.1:free` (`poolside/...:free`) | Poolside | **262K** (262.144) | **32K** (32.768) | `minimal…xhigh`\* | ❌ |
@@ -258,10 +260,15 @@ Keyless access. Short aliases work for every row (the full ID is in parentheses)
258
260
  | `kilo-auto` (`kilo-auto/free`) | Kilo Gateway Auto | **256K** (256.000) | **10K** (10.000) | `minimal…xhigh`\* | ❌ |
259
261
  | `openrouter` (`openrouter/free`) | OpenRouter Free | **200K** (200.000) | **65K** (65.536) | `minimal…xhigh`\* | ✅ |
260
262
  | `glyph-cluster` (`stealth/glyph-cluster`) | Stealth preview (lab undisclosed) | **256K** (256.000) | **256K** (256.000) | `minimal…xhigh`\* | ❌ |
263
+ | `minimax/minimax-m3.1-flash:free` | MiniMax | **1M** (1.000.000) | **512K** (524.288) | `low…max`\* | ✅ |
264
+ | `nvidia/nemotron-3.5-super-vl:free` | NVIDIA | **1M** (1.000.000) | **131K** (131.072) | `minimal…xhigh`\* | ✅ |
265
+ | `beam-kilo` (`reflection/beam-free`) | Reflection AI | **1M** (1.048.576) | **131K** (131.072) | `low…max`\* | ❌ |
266
+ | `flint-kilo` (`recursive/flint-v1.0:free`) | Recursive | **262K** (262.144) | **65K** (65.536) | `minimal…xhigh`\* | ❌ |
267
+ | `xing4.0` (`xingchen-agi/xing4.0-29b-a4b:free`) | XingChen | **262K** (262.144) | **235K** (235.929) | `minimal…xhigh`\* | ❌ |
261
268
 
262
269
  \* Levels are forwarded as-is through the OpenRouter-style nested `reasoning` parameter; effort mapping is decided by each model. MiMo collapses `minimal→low` and `xhigh→high` upstream, so its selector shows 5 labels but only 3 distinct effort values.
263
270
 
264
- #### Cline (2 models), OpenAI compatible
271
+ #### Cline (1 model), OpenAI compatible
265
272
 
266
273
  Rotating per-account promo, direct only (never through the relay pool). Sign in through your browser with `/freeflow cline login`, manage with `/freeflow cline accounts` and `/freeflow cline logout`. Requests roll across saved logins when one hits its daily free limit.
267
274
 
@@ -271,7 +278,6 @@ Each account can only be saved once: an extra login for an account you already h
271
278
 
272
279
  | Model ID | Creator / Lab | Context | Max Output | Thinking | Vision |
273
280
  | :--- | :--- | :--- | :--- | :--- | :--- |
274
- | `cline-free/muse-spark-1.3-contributor` | Meta Superintelligence Labs | **1M** (1.048.576) | **131K** (131.072) | `minimal…xhigh`\* | ✅ |
275
281
  | `z-ai/glm-5.3-flash` | Zhipu AI | **1M** (1.048.576) | **131K** (131.072) | `low/high/max` | ✅ |
276
282
  #### OVHcloud AI Endpoints (8 models), OpenAI compatible
277
283
 
@@ -303,13 +309,21 @@ cat ~/.pi/agent/pi-freeflow.log | tail -n 50
303
309
  /freeflow debug on
304
310
  ```
305
311
 
312
+ **Tool-call audit.** A streamed turn that declares tools — and a non-streamed Chat Completions turn — also writes a line to `~/.pi/agent/toolcall-audit.jsonl`, so a turn the host abandoned or retried can be explained without reproducing it:
313
+
314
+ - `summary` — one line per finished turn: the tool names the caller declared, the calls the model made (argument byte size and whether they parsed), the finish reason, and a classified failure when there was one.
315
+ - `attempt-failure` — one line per upstream attempt abandoned in favour of another, with the reason (rate limit, timeout, payload too large, rejected relay, final upstream verdict) and the HTTP status when one arrived.
316
+ - `retry-detected` — the host re-sent a conversation the proxy had already seen; `retryOf` names the earlier turn and carries its failure.
317
+
318
+ The log holds names, sizes, booleans and reasons only: no message text, no tool schemas, no argument values. It rotates once to `toolcall-audit.jsonl.1` at 5MB, and a turn that declared no tools writes nothing. `docs/pages/architecture.md` documents every field and has jq recipes for the common questions.
319
+
306
320
  ---
307
321
 
308
322
  ### Design
309
323
 
310
- This package stays thin. It ships three things: a model catalog, a relay proxy, and a log. There is no build step and one runtime dependency (`undici`). Thinking and prompt normalization stay with the host (`pi-ai`).
324
+ This package stays thin. It ships three things: a model catalog, a relay proxy, and logs. There is no build step and one runtime dependency (`undici`). Thinking and prompt normalization stay with the host (`pi-ai`).
311
325
 
312
- About 52k lines including tests: `src/` is roughly 19.4k across 30 files, and the 90 test files add roughly 32.4k more. The full suite (sandboxed, network-mocked) and typecheck pass before every release. See CHANGELOG.md.
326
+ About 57k lines including tests: `src/` is roughly 21.1k across 31 files, and the 93 test files add roughly 35.6k more. The full suite (sandboxed, network-mocked) and typecheck pass before every release. See CHANGELOG.md.
313
327
 
314
328
  ---
315
329
 
@@ -382,7 +396,7 @@ pnpm smoke # verifies extensions/index.ts loads without crashing
382
396
  #### Project structure
383
397
 
384
398
  ```
385
- ├── models.ts # 32-model catalog definitions
399
+ ├── models.ts # 37-model catalog definitions
386
400
  ├── index.ts # extension entry, lifecycle hooks
387
401
  ├── catalog.ts # model catalog cache (24h disk)
388
402
  ├── proxy.ts # local proxy server (127.0.0.1:28180)
@@ -398,6 +412,7 @@ pnpm smoke # verifies extensions/index.ts loads without crashing
398
412
  ├── probe.ts # relay reachability and latency probe
399
413
  ├── upstream-health.ts # Zen free-tier gating watch and Kilo failover
400
414
  ├── stream-pipe.ts # SSE stream piping and truncation resilience
415
+ ├── toolcall-audit.ts # per-turn tool-call audit log (names and sizes only)
401
416
  ├── opencode-fingerprint.ts # Zen free-tier client fingerprint enforcement
402
417
  ├── system-ca-fetch.ts # OS-trust-store retry for intercepted TLS
403
418
  ├── commands.ts # /freeflow CLI subcommands
@@ -405,7 +420,9 @@ pnpm smoke # verifies extensions/index.ts loads without crashing
405
420
  ├── cline-accounts.ts # per-slot Cline key pool with failover
406
421
  ├── cline-device-auth.ts # Cline WorkOS device-flow login
407
422
  ├── update-checker.ts # npm registry version check, cached
423
+ ├── update-core.ts # version facts and update step runner
408
424
  ├── updater.ts # package-manager choice for /freeflow update
425
+ ├── atomic-write.ts # temp-file + rename writes, retried across Windows locks
409
426
  ├── config.ts # constants, whitelists, paths, and runtime settings
410
427
  ├── logger.ts # file logger with 10MB rotation
411
428
  └── types.ts # shared type definitions
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.40.1",
4
+ "version": "1.41.0",
5
5
  "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
6
  "main": "extensions/index.ts",
7
7
  "types": "src/index.ts",
package/src/catalog.ts CHANGED
@@ -45,12 +45,11 @@ import type {
45
45
  * - mimo-v2.5-free: decommissioned upstream (2026-10-07) — HTTP 401 "Model mimo-v2.5-free is not supported".
46
46
  * Re-probed 2026-10-16: 410 "Model mimo-v2.5-free has been deprecated. Use
47
47
  * mimo-v2.6-flash-free instead." The docs still list it as free; they are stale.
48
- * - exo-free: dead-listed 2026-10-09 (410 "Model exo-free has been deprecated")
49
- * but revived upstream on 2026-10-16 — back on the official free lineup and
50
- * answering live through the proxy (chat 200 with tools, stream, reasoning,
51
- * vision; dual-canary recall at ~520K tokens; a 1.2M-word prompt draws the
52
- * empty-bodied 400). Re-registered statically the same day: do NOT add this
53
- * id back to this set unless it dies again.
48
+ * - exo-free: dead-listed 2026-10-09 (410 "Model exo-free has been deprecated"),
49
+ * revived upstream on 2026-10-16, and dead again on 2026-10-22: the endpoint
50
+ * answers 410 "Model exo-free has been deprecated" on every path through the
51
+ * relay pool and the id is absent from the live Zen listing. Permanently
52
+ * dead-listed so no disk cache or upstream merge can put it back.
54
53
  * - fledge-alpha-free: withdrawn upstream (2026-10-09) — absent from the live
55
54
  * Zen list, chat 401 "Model fledge-alpha-free is not supported".
56
55
  * - inclusionai/ling-3.0-flash-sante:free: demoted to paid on Kilo (2026-10-09) —
@@ -75,10 +74,10 @@ import type {
75
74
  * retired upstream (2026-10-13) — probed live twice each against the real
76
75
  * Cline pool, every attempt answered HTTP 404 `{"error":"model not found",
77
76
  * "success":false,"hint":"Cline has no such model. Refresh the model list and
78
- * retry."}`, while the two Cline models that survive (cline-free/
79
- * muse-spark-1.3-contributor and z-ai/glm-5.3-flash) answered 200 with the
80
- * same account and request shape. The lane is healthy; these three ids are
81
- * gone, so they must never re-enter the picker from disk cache or an
77
+ * retry."}`, while the sibling `z-ai/glm-5.3-flash` (and, until its own
78
+ * retirement below, `cline-free/muse-spark-1.3-contributor`) answered 200
79
+ * with the same account and request shape. The lane is healthy; these three
80
+ * ids are gone, so they must never re-enter the picker from disk cache or an
82
81
  * upstream merge.
83
82
  */
84
83
  export const DEAD_MODEL_IDS = new Set<string>([
@@ -136,8 +135,17 @@ export const DEAD_MODEL_IDS = new Set<string>([
136
135
  // muse-spark-1.2-contributor-free: decommissioned upstream (2026-10-18) — answers
137
136
  // HTTP 410 "Model muse-spark-1.2-contributor-free has been deprecated" while the
138
137
  // same request shape on the surviving Zen models answers 200. Retired from the
139
- // static catalog the same day; the Cline Muse Spark 1.3 entry is unaffected.
138
+ // static catalog the same day.
140
139
  "muse-spark-1.2-contributor-free",
140
+ // cline-free/muse-spark-1.3-contributor: gone from Cline's model inventory
141
+ // (2026-10-22) — three live probes through the real pool, spaced over ~10
142
+ // minutes, each answered HTTP 404 `{"error":"model not found",
143
+ // "success":false,"hint":"Cline has no such model. Refresh the model list and
144
+ // retry."}`, while `z-ai/glm-5.3-flash` resolved on the same account (its 402
145
+ // insufficient_credits is raised after model resolution). The id was the last
146
+ // entry declaring `api: "openai-responses"`; it stays retired so a stale disk
147
+ // cache cannot put it back in the picker.
148
+ "cline-free/muse-spark-1.3-contributor",
141
149
  // nvidia/nemotron-3-super-120b-a12b:free: dead route (2026-10-21) — Kilo answers
142
150
  // 404 "Function '<uuid>': Not found for account ..." from its Nvidia provider on
143
151
  // every attempt, from two independent egress IPs, while sibling Nvidia ids answer
@@ -147,6 +155,10 @@ export const DEAD_MODEL_IDS = new Set<string>([
147
155
  // HTTP 404 "The requested model is currently unavailable" (unavailable_model) on
148
156
  // every attempt, from two independent egress IPs; it is no longer served.
149
157
  "thinkingmachines/inkling-small:free",
158
+ // exo-free: re-deprecated upstream (2026-10-22) — 410 ModelDeprecated on every
159
+ // path and gone from the live Zen listing, four days after it was re-registered
160
+ // from a 2026-10-16 revival. Permanently dead-listed.
161
+ "exo-free",
150
162
  ]);
151
163
  /**
152
164
  * Free-tier allowlist for anything entering the picker via network or stale disk.
@@ -163,10 +175,10 @@ export function isFreeCatalogId(id: string): boolean {
163
175
  /**
164
176
  * Purge paid/dead entries from a catalog list and repair known models against
165
177
  * the static definitions. Stale disk caches predate the paid filter and the
166
- * muse-spark-1.3 responses-api entry, so loading them verbatim replays a wrong
167
- * api (chat/completions for a responses-only model -> upstream 500) until the
168
- * 24h TTL expires. Repairing here self-heals on the next refresh without a
169
- * reinstall.
178
+ * responses-api entries (the last one, Cline's Muse Spark 1.3, retired on
179
+ * 2026-10-22), so loading them verbatim replays a wrong api (chat/completions
180
+ * for a responses-only model -> upstream 500) until the 24h TTL expires.
181
+ * Repairing here self-heals on the next refresh without a reinstall.
170
182
  */
171
183
  export function sanitizeCatalogModels(models: RegisteredModel[]): RegisteredModel[] {
172
184
  const out: RegisteredModel[] = [];
@@ -186,7 +198,7 @@ export function sanitizeCatalogModels(models: RegisteredModel[]): RegisteredMode
186
198
  }
187
199
  /**
188
200
  * In-memory cache of currently active/available free models.
189
- * Initialized with all 32 verified models for 0ms instant availability.
201
+ * Initialized with all 37 verified models for 0ms instant availability.
190
202
  */
191
203
  let aliveCatalog: RegisteredModel[] = ALL_MODELS.map((m) => ({
192
204
  ...m,
@@ -305,11 +317,11 @@ export function enrichModelDef(raw: RawModelItem, source: Upstream): RegisteredM
305
317
  maxTokens = 131_072;
306
318
  }
307
319
 
308
- // Muse Spark is served on the responses API upstream (the Cline entry still
309
- // declares it; the Zen 1.2/1.3 contributor ids were retired upstream). Every
310
- // known id short-circuits above, so this only decides for ids Zen lists
311
- // dynamically — matching the family keeps a future muse-spark id off the chat
312
- // endpoint it would 500 on.
320
+ // Muse Spark was served on the responses API upstream: the Cline entry
321
+ // declared it until 2026-10-22, and the Zen 1.2/1.3 contributor ids retired
322
+ // before that. Every known id short-circuits above, so this only decides for
323
+ // ids Zen lists dynamically — matching the family keeps a future muse-spark
324
+ // id off the chat endpoint it would 500 on.
313
325
  const isResponses = idLower.includes("muse-spark");
314
326
 
315
327
  return {
@@ -130,9 +130,10 @@ const CLINE_CLIENT_VERSION = "3.5.54";
130
130
  * `cline-free/*` id space (measured live 2026-09-22: that prefix answers 403
131
131
  * "only available via Cline product surfaces" with a bare bearer, and 200 with
132
132
  * this set; every non-prefixed id answers 200 either way). The header is kept
133
- * because one catalog id still depends on it — after the 2026-10-13 prune,
134
- * `cline-free/muse-spark-1.3-contributor` is the last prefixed model in the
135
- * catalog. It also decided the free-model feed at the time: five entries for
133
+ * because `cline-free/*` ids can still appear in a stale host registration —
134
+ * until 2026-10-13 that prefix covered four models, and
135
+ * `cline-free/muse-spark-1.3-contributor` survived it until 2026-10-22. It also
136
+ * decided the free-model feed at the time: five entries for
136
137
  * `cline-sdk` / `cline-cli` / the `VSCode Extension` label against six for
137
138
  * `cline-desktop`, the extra one being `cline-free/kimi-k3`. That sixth model
138
139
  * is retired upstream and no longer requested. Version and User-Agent stay
package/src/config.ts CHANGED
@@ -450,6 +450,19 @@ export function resolveUpstreamHealthPath(): string {
450
450
  }
451
451
  }
452
452
 
453
+ /** JSONL audit of tool-call turns that failed or needed repair (src/toolcall-audit.ts). */
454
+ export function resolveToolcallAuditPath(): string {
455
+ try {
456
+ return dataFilePath("toolcall-audit.jsonl");
457
+ } catch {
458
+ return path.join(
459
+ path.dirname(fileURLToPath(import.meta.url)),
460
+ "..",
461
+ ".toolcall-audit.jsonl",
462
+ );
463
+ }
464
+ }
465
+
453
466
  export function resolveSessionPinPath(): string {
454
467
  try {
455
468
  return dataFilePath("pi-freeflow-session-pins.json");