pi-freeflow 1.4.3 → 1.4.4
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 +5 -7
- package/package.json +1 -1
- package/src/catalog.ts +38 -10
- package/src/index.ts +1 -1
- package/src/models.ts +12 -56
- package/src/proxy.ts +4 -14
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-freeflow 🌊
|
|
2
2
|
|
|
3
|
-
> **
|
|
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
|
-
| **
|
|
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
|
-
###
|
|
35
|
+
### 21 Curated Models, One Command
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
/model → freeflow → pick
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
#### OpenCode Zen (
|
|
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` | ❌ |
|
|
@@ -285,7 +283,7 @@ pnpm smoke # verifies extensions/index.ts loads without crashing
|
|
|
285
283
|
```
|
|
286
284
|
src/
|
|
287
285
|
├── index.ts # extension entry, lifecycle hooks
|
|
288
|
-
├── models.ts #
|
|
286
|
+
├── models.ts # 21-model catalog definitions
|
|
289
287
|
├── catalog.ts # model catalog cache (24h disk)
|
|
290
288
|
├── proxy.ts # local proxy server (127.0.0.1:18080)
|
|
291
289
|
├── 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.
|
|
4
|
+
"version": "1.4.4",
|
|
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
|
|
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
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
216
|
-
|
|
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 —
|
|
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
|
|
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
|
|
5
|
-
* -
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
92
|
-
*
|
|
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(
|
|
95
|
-
|
|
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.
|