pi-freeflow 1.4.3 → 1.4.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-freeflow 🌊
2
2
 
3
- > **23 free models. Up to 1M context. Zero API keys. Infinite scale via your own relay pool.**
3
+ > **21 free models. Up to 1M context. Zero API keys. Infinite scale via your own relay pool.**
4
4
 
5
5
  Thin by design: model list + dumb relay + log. Host `pi-ai` owns thinking, normalization & provider magic. We just make it free, fast, and unbreakable.
6
6
 
@@ -18,7 +18,7 @@ Join devs bypassing rate limits with their own relay pools. BYO, add as many as
18
18
 
19
19
  | Feature | Description | Value | Cost |
20
20
  | :--- | :--- | :--- | :--- |
21
- | **23 Curated Free Models** | 9 OpenCode Zen + 14 KiloCode Gateway models, up to 1M context & 512K output | Ceiling Unlocked | **$0** |
21
+ | **21 Curated Free Models** | 7 OpenCode Zen + 14 KiloCode Gateway models, up to 1M context & 512K output | Ceiling Unlocked | **$0** |
22
22
  | **BYO Relay Pool** | Round-robin load balancing across your Cloudflare Workers & Vercel Edges | Zero Rate Limits | **$0** (your free tiers) |
23
23
  | **Adaptive Health & Error Detection** | Auto-cooldown on 429 rate limits, 504 timeouts, and socket drops | 0ms Wasted Latency | **$0** |
24
24
  | **Stream Truncation Resilience** | Stateful SSE terminal tracking (`response.failed` / `response.incomplete` injection) | Zero Host Crashes | **$0** |
@@ -32,21 +32,19 @@ Philosophy: **Thin by design.** We only ship model list + relay proxy + log. Hos
32
32
 
33
33
  ---
34
34
 
35
- ### 23 Curated Models, One Command
35
+ ### 21 Curated Models, One Command
36
36
 
37
37
  ```bash
38
38
  /model → freeflow → pick
39
39
  ```
40
40
 
41
- #### OpenCode Zen (9 Models), Responses & Chat API
41
+ #### OpenCode Zen (7 Models), Responses & Chat API
42
42
  Optimized for deep reasoning, long-horizon coding & autonomous agentic workflows.
43
43
 
44
44
  | Model ID | Creator / Lab | Context | Max Output | Thinking | Vision |
45
45
  | :--- | :--- | :--- | :--- | :--- | :--- |
46
46
  | `muse-spark-1.2-contributor-free` | Meta Superintelligence Labs | **1M** (1.048.576) | **131K** (131.072) | `minimal / low / medium / high / xhigh / max` | ✅ |
47
- | `x-preview-f-free` | Ox Alpha | **1M** (1.048.576) | **131K** (131.072) | `low / high / max` | ✅ |
48
47
  | `mimo-v2.5-free` | Xiaomi MiMo | **1M** (1.048.576) | **131K** (131.072) | `low / medium / high` | ✅ |
49
- | `deepseek-v4-flash-free` | DeepSeek | **1M** (1.000.000) | **384K** (384.000) | `low / high / max` | ❌ |
50
48
  | `laguna-s-2.1-free` | Poolside | **1M** (1.048.576) | **131K** (131.072) | `low / high / max` | ❌ |
51
49
  | `nemotron-3.5-lightning-free` | NVIDIA | **1M** (1.000.000) | **262K** (262.144) | `low / high / max` | ❌ |
52
50
  | `nemotron-3-ultra-free` | NVIDIA | **1M** (1.000.000) | **128K** (128.000) | `low / high / max` | ❌ |
@@ -107,7 +105,7 @@ Manage your relay pool directly from the OMP / Pi terminal:
107
105
  /freeflow label <index|url> <name># Assign a friendly label to a relay
108
106
  /freeflow remove <index|url|label># Remove a relay from the pool
109
107
  /freeflow on | off | auto # Toggle relay mode (auto = enabled for freeflow)
110
- /freeflow deploy vercel # Guided Vercel Edge relay deploy (also: deploy cloudflare, deploy deno)
108
+ /freeflow deploy <platform> # Guided relay deploy: vercel|cloudflare|deno — token in-memory, auto-adds (Vercel 1M/mo recommended)
111
109
  /freeflow logs [lines] # Inspect recent proxy logs
112
110
  /freeflow debug on | off # Toggle full HTTP lifecycle debug logging
113
111
  ```
@@ -148,9 +146,11 @@ Default ships direct. Add relays via `/freeflow add <url> [label]`.
148
146
 
149
147
  **Zero setup?** Run `/freeflow deploy cloudflare` (or `deno`, `vercel`), paste your platform token once, and the relay is created and activated for you. Manual snippets below.
150
148
 
151
- **Option A: Cloudflare Workers (100k req/day, no 25s timeout)**
152
- 1. Go to `dash.cloudflare.com` → Workers → Create → Deploy → Edit code
153
- 2. Paste this Worker relay snippet:
149
+ **Option A: Cloudflare Workers (100k req/day, no 25s timeout) — Auto Deploy**
150
+ ```bash
151
+ /freeflow deploy cloudflare # prompts token in-memory, auto-adds to pool
152
+ ```
153
+ *Manual fallback:* `dash.cloudflare.com` → Workers → Create → Deploy → Edit code → paste snippet below → Deploy → `/freeflow add https://your.workers.dev cf-worker-1`
154
154
 
155
155
  ```js
156
156
  // Only the 2 upstreams pi-freeflow talks to. Anything else = open proxy abuse.
@@ -170,20 +170,18 @@ export default {
170
170
  },
171
171
  };
172
172
  ```
173
- 3. Deploy and add it:
174
173
 
174
+ **Option B: Vercel Edge Relay (1M req/mo) — Auto Deploy**
175
175
  ```bash
176
- omp → /freeflow add https://your.workers.dev cf-worker-1
176
+ /freeflow deploy vercel # prompts token in-memory, auto-adds to pool
177
+ # or shorthand: /freeflow deploy
177
178
  ```
178
179
 
179
- **Option B: Vercel Edge Relay (1M req/mo), Auto Deploy**
180
+ **Option C: Deno Deploy (100k req/day) Auto Deploy**
180
181
  ```bash
181
- /freeflow deploy # prompts token in-memory, auto-adds to pool
182
+ /freeflow deploy deno # prompts token in-memory, auto-adds to pool
182
183
  ```
183
-
184
- **Option C: Deno Deploy (100k req/day)**
185
- 1. Go to `dash.deno.com` → New Project → Playground
186
- 2. Paste this relay and click **Deploy**:
184
+ *Manual fallback:* `dash.deno.com` → New Project → Playground → paste snippet below → Deploy → `/freeflow add https://your-project.deno.dev deno-relay-1`
187
185
 
188
186
  ```ts
189
187
  const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
@@ -201,12 +199,6 @@ Deno.serve(async (req) => {
201
199
  });
202
200
  ```
203
201
 
204
- 3. Add it:
205
-
206
- ```bash
207
- /freeflow add https://your-project.deno.dev deno-relay-1
208
- ```
209
-
210
202
  **Verify your pool:**
211
203
  ```bash
212
204
  /freeflow status # relay-A 1/N (ON) → candidates:N
@@ -285,7 +277,7 @@ pnpm smoke # verifies extensions/index.ts loads without crashing
285
277
  ```
286
278
  src/
287
279
  ├── index.ts # extension entry, lifecycle hooks
288
- ├── models.ts # 23-model catalog definitions
280
+ ├── models.ts # 21-model catalog definitions
289
281
  ├── catalog.ts # model catalog cache (24h disk)
290
282
  ├── proxy.ts # local proxy server (127.0.0.1:18080)
291
283
  ├── relay.ts # relay selection & round-robin
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.4.3",
4
+ "version": "1.4.5",
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
@@ -31,9 +31,14 @@ import type {
31
31
  Upstream,
32
32
  } from "./types.ts";
33
33
 
34
+ /**
35
+ * Pruned model IDs that must never re-enter the catalog via disk cache or upstream merge.
36
+ */
37
+ const DEAD_MODEL_IDS = new Set<string>(["deepseek-v4-flash-free", "x-preview-f-free"]);
38
+
34
39
  /**
35
40
  * In-memory cache of currently active/available free models.
36
- * Initialized with all 23 verified models for 0ms instant availability.
41
+ * Initialized with all 21 verified models for 0ms instant availability.
37
42
  */
38
43
  let aliveCatalog: RegisteredModel[] = ALL_MODELS.map((m) => ({
39
44
  ...m,
@@ -58,14 +63,17 @@ export function setAliveCatalog(catalog: RegisteredModel[]): void {
58
63
  * Fresh entries win on id collision; unknown fresh ids are appended after the base.
59
64
  * Guards the background refresh against a partial upstream cache removing
60
65
  * verified static models from provider registration.
66
+ * Filters pruned dead IDs so they never re-enter via fresh upstream data.
61
67
  */
62
68
  export function mergeCatalog(
63
69
  base: RegisteredModel[],
64
70
  fresh: RegisteredModel[],
65
71
  ): RegisteredModel[] {
72
+ const filteredFresh = fresh.filter((m) => !DEAD_MODEL_IDS.has(m.id));
66
73
  const byId = new Map(base.map((m) => [m.id, m]));
67
- for (const m of fresh) byId.set(m.id, m);
68
- return [...byId.values()];
74
+ for (const m of filteredFresh) byId.set(m.id, m);
75
+ // Ensure no dead IDs survive even if base was stale
76
+ return [...byId.values()].filter((m) => !DEAD_MODEL_IDS.has(m.id));
69
77
  }
70
78
 
71
79
  /**
@@ -122,7 +130,6 @@ export function enrichModelDef(raw: RawModelItem, source: Upstream): RegisteredM
122
130
  idLower.includes("r1") ||
123
131
  idLower.includes("o1") ||
124
132
  idLower.includes("think") ||
125
- idLower.includes("alpha") ||
126
133
  idLower.includes("spark");
127
134
 
128
135
  let contextWindow =
@@ -162,6 +169,7 @@ export function enrichModelDef(raw: RawModelItem, source: Upstream): RegisteredM
162
169
 
163
170
  /**
164
171
  * Read cached catalog data from disk if valid and unexpired.
172
+ * Filters out pruned dead IDs so stale disk entries never repopulate the catalog.
165
173
  */
166
174
  export function readCatalogCache(): CatalogCacheData | null {
167
175
  try {
@@ -170,6 +178,9 @@ export function readCatalogCache(): CatalogCacheData | null {
170
178
  }
171
179
  const raw = fs.readFileSync(CATALOG_CACHE_FILE, "utf8");
172
180
  const data = JSON.parse(raw) as CatalogCacheData;
181
+ if (Array.isArray(data.models)) {
182
+ data.models = data.models.filter((m) => !DEAD_MODEL_IDS.has(m.id));
183
+ }
173
184
  if (Date.now() - data.timestamp < CATALOG_CACHE_TTL_MS) {
174
185
  return data;
175
186
  }
@@ -203,20 +214,37 @@ export function writeCatalogCache(data: CatalogCacheData): void {
203
214
  export async function refreshCatalog(force = false): Promise<RegisteredModel[]> {
204
215
  // Thin provider: no live fetch — subagents must not hit upstream directly
205
216
  // (proxy-only). Host Pi/OMP owns dynamic discovery via fetchDynamicModels (24h).
206
- // We only serve disk cache if fresh, otherwise static 23-model aliveCatalog.
217
+ // We only serve disk cache if fresh, otherwise static 21-model aliveCatalog.
207
218
  const disk = readCatalogCache();
208
219
  if (disk && Array.isArray(disk.models) && disk.models.length > 0) {
209
220
  const age = Date.now() - (disk.timestamp ?? 0);
210
221
  if (!force && age < CATALOG_CACHE_TTL_MS) {
211
- aliveCatalog = disk.models;
222
+ aliveCatalog = disk.models.filter((m) => !DEAD_MODEL_IDS.has(m.id));
212
223
  return aliveCatalog;
213
224
  }
214
- // Stale cache still better than empty — return it without network
215
- if (disk.models.length >= 23) {
216
- aliveCatalog = disk.models;
225
+ // Stale cache still better than empty — return it without network (filtered)
226
+ const filtered = disk.models.filter((m) => !DEAD_MODEL_IDS.has(m.id));
227
+ if (filtered.length >= 21) {
228
+ aliveCatalog = filtered;
217
229
  return aliveCatalog;
218
230
  }
219
231
  }
220
- // No valid cache — return in-memory static 23 (host will refresh if needed)
232
+ // No valid fresh cache — try stale disk cache directly (readCatalogCache returns null when expired)
233
+ try {
234
+ if (fs.existsSync(CATALOG_CACHE_FILE)) {
235
+ const raw = fs.readFileSync(CATALOG_CACHE_FILE, "utf8");
236
+ const stale = JSON.parse(raw) as CatalogCacheData;
237
+ if (Array.isArray(stale.models) && stale.models.length > 0) {
238
+ const filtered = stale.models.filter((m) => !DEAD_MODEL_IDS.has(m.id));
239
+ if (filtered.length >= 21) {
240
+ aliveCatalog = filtered;
241
+ return aliveCatalog;
242
+ }
243
+ }
244
+ }
245
+ } catch (err) {
246
+ logDebug("Failed reading stale catalog cache", { error: String(err) });
247
+ }
248
+ // No valid cache — return in-memory static 21 (host will refresh if needed)
221
249
  return aliveCatalog;
222
250
  }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * pi-freeflow — Modular, high-resiliency LLM extension for Pi & Oh My Pi (OMP)
3
3
  *
4
- * Provides access to 23 free models (9 OpenCode Zen + 14 KiloCode Gateway) with:
4
+ * Provides access to 21 free models (7 OpenCode Zen + 14 KiloCode Gateway) with:
5
5
  * - Single-port daemon reuse on 18080 across concurrent subagents
6
6
  * - Multi-cloud rolling egress relays (Vercel Edge, Cloudflare, Deno)
7
7
  * - 0ms instant startup with verified static catalog and background live health checks
package/src/models.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Static model definitions and upstream routing catalogs for pi-freeflow
3
3
  *
4
- * Defines the 23 verified free models:
5
- * - 9 OpenCode Zen models (1 Responses API + 8 Chat Completions)
4
+ * Defines the 21 verified free models:
5
+ * - 7 OpenCode Zen models (1 Responses API + 6 Chat Completions)
6
6
  * - 14 KiloCode Keyless Gateway models (10 OpenRouter format + 4 Standard format)
7
7
  */
8
8
 
@@ -13,40 +13,6 @@ import type { ModelDef, Upstream } from "./types.ts";
13
13
  * Endpoint: https://opencode.ai/zen/v1
14
14
  */
15
15
  export const OPENCODE_MODELS: ModelDef[] = [
16
- {
17
- id: "deepseek-v4-flash-free",
18
- name: "DeepSeek V4 Flash (1M)",
19
- reasoning: true,
20
- contextWindow: 1_000_000,
21
- maxTokens: 384_000,
22
- input: ["text"],
23
- thinkingLevelMap: {
24
- off: null,
25
- minimal: "low",
26
- low: "low",
27
- medium: "high",
28
- high: "high",
29
- xhigh: "max",
30
- max: "max",
31
- },
32
- },
33
- {
34
- id: "x-preview-f-free",
35
- name: "Ox Alpha (1M)",
36
- reasoning: true,
37
- contextWindow: 1_048_576,
38
- maxTokens: 131_072,
39
- input: ["text", "image"],
40
- thinkingLevelMap: {
41
- off: null,
42
- minimal: "low",
43
- low: "low",
44
- medium: "high",
45
- high: "high",
46
- xhigh: "max",
47
- max: "max",
48
- },
49
- },
50
16
  {
51
17
  id: "muse-spark-1.2-contributor-free",
52
18
  name: "Muse Spark 1.2 (1M)",
@@ -305,37 +271,27 @@ export const KILO_MODELS: ModelDef[] = [
305
271
 
306
272
  /**
307
273
  * Model ID Aliases — maps user-friendly / slash-free CLI IDs to canonical upstream model IDs.
274
+ * Single clean alias per model (no :free duplicates). Wrong cross-lab aliases removed:
275
+ * - Muse Spark 1.2 = Meta Superintelligence Labs (not Anthropic Claude) → removed claude-sonnet aliases
276
+ * - Laguna S 2.1 = Poolside (not MiniMax) → removed minimax-m2.1 alias
277
+ * - Hy3 = Tencent Hunyuan (not Alibaba Qwen) → removed qwen3-coder alias
308
278
  */
309
279
  export const MODEL_ALIASES: Record<string, string> = {
310
- // Kilo Gateway slash-free & colon-free CLI aliases
280
+ // Kilo Gateway slash-free & colon-free clean aliases (one per model)
311
281
  "dots-3-note-preview": "dots-studio/dots-3-note-preview:free",
312
- "dots-3-note-preview:free": "dots-studio/dots-3-note-preview:free",
313
282
  "step-3.7-flash": "stepfun/step-3.7-flash:free",
314
- "step-3.7-flash:free": "stepfun/step-3.7-flash:free",
315
283
  "nemotron-3-nano-omni": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
316
- "nemotron-3-nano-omni:free": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
317
284
  "nemotron-3-ultra-550b": "nvidia/nemotron-3-ultra-550b-a55b:free",
318
- "nemotron-3-ultra-550b:free": "nvidia/nemotron-3-ultra-550b-a55b:free",
319
285
  "nemotron-3-super": "nvidia/nemotron-3-super-120b-a12b:free",
320
- "nemotron-3-super:free": "nvidia/nemotron-3-super-120b-a12b:free",
321
- "hy3:free": "tencent/hy3:free",
322
286
  "north-mini-code": "cohere/north-mini-code:free",
323
- "north-mini-code:free": "cohere/north-mini-code:free",
324
- "laguna-s-2.1:free": "poolside/laguna-s-2.1:free",
325
- "laguna-xs-2.1:free": "poolside/laguna-xs-2.1:free",
326
287
  "lfm-2.5": "liquid/lfm-2.5-2.6b:free",
327
- "lfm-2.5:free": "liquid/lfm-2.5-2.6b:free",
328
288
  "content-safety": "nvidia/nemotron-3.5-content-safety:free",
329
- "content-safety:free": "nvidia/nemotron-3.5-content-safety:free",
289
+ // provider-prefixed short aliases (slash-normalized)
290
+ "hy3:free": "tencent/hy3:free",
291
+ "laguna-s-2.1:free": "poolside/laguna-s-2.1:free",
292
+ "laguna-xs-2.1:free": "poolside/laguna-xs-2.1:free",
330
293
  "kilo-auto": "kilo-auto/free",
331
294
  "openrouter": "openrouter/free",
332
-
333
- // OpenCode Zen aliases
334
- "claude-sonnet-4.5-free": "muse-spark-1.2-contributor-free",
335
- "claude-sonnet-4.5-contributor-free": "muse-spark-1.2-contributor-free",
336
- "grok-code-fast-1-preview-f-free": "x-preview-f-free",
337
- "minimax-m2.1-free": "laguna-s-2.1-free",
338
- "qwen3-coder-480b-free": "hy3-free",
339
295
  };
340
296
 
341
297
  /**
@@ -357,7 +313,7 @@ export const KILO_MODEL_IDS = new Set<string>([
357
313
  ]);
358
314
 
359
315
  /**
360
- * Combined list of all 23 static free models (canonical)
316
+ * Combined list of all 21 static free models (canonical)
361
317
  */
362
318
  export const ALL_MODELS: ModelDef[] = [...OPENCODE_MODELS, ...KILO_MODELS];
363
319
 
package/src/proxy.ts CHANGED
@@ -88,21 +88,11 @@ export function sanitizeHeaders(
88
88
  }
89
89
 
90
90
  /**
91
- * Clamps reasoning_effort for upstream models with strict non-standard enums
92
- * (e.g. OpenCode x-preview strictly requires 'low', 'high', or 'max' and rejects 'medium' with 400).
91
+ * Reasoning normalization is owned by host pi-ai; proxy passes through reasoning fields unchanged.
92
+ * Kept as no-op for compatibility no per-model clamping.
93
93
  */
94
- function sanitizeReasoningForModel(bodyObj: Record<string, unknown>): void {
95
- const model = String(bodyObj.model || "").toLowerCase();
96
- if (model.includes("x-preview")) {
97
- const effort = String(bodyObj.reasoning_effort || "").toLowerCase();
98
- if (effort === "medium") {
99
- bodyObj.reasoning_effort = "high";
100
- } else if (effort === "minimal") {
101
- bodyObj.reasoning_effort = "low";
102
- } else if (!effort || effort === "off" || effort === "none") {
103
- bodyObj.reasoning_effort = "low";
104
- }
105
- }
94
+ function sanitizeReasoningForModel(_bodyObj: Record<string, unknown>): void {
95
+ // no-op
106
96
  }
107
97
  /**
108
98
  * Probe whether an existing pi-freeflow proxy daemon is running and responsive on a given port.