pi-freeflow 1.3.1 → 1.3.6
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 +149 -227
- package/package.json +2 -2
- package/src/catalog.ts +8 -5
- package/src/commands.ts +242 -92
- package/src/index.ts +47 -14
- package/src/models.ts +72 -8
- package/src/proxy.ts +31 -10
- package/src/relay-state.ts +125 -21
- package/src/types.ts +2 -1
package/README.md
CHANGED
|
@@ -1,277 +1,199 @@
|
|
|
1
|
-
# pi-freeflow
|
|
1
|
+
# pi-freeflow 🌊
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **23 free models. Up to 1M context. Zero API keys. Infinite scale via your own relay pool.**
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/pi-freeflow)
|
|
8
|
+
[](https://www.npmjs.com/package/pi-freeflow)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://github.com/badlogic/pi-ai)
|
|
11
|
+
[](https://github.com/coder/oh-my-pi)
|
|
12
|
+
|
|
13
|
+
**Join devs bypassing rate limits with their own relay pools — BYO, add as many as you need.**
|
|
4
14
|
|
|
5
15
|
---
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
### Why FreeFlow?
|
|
18
|
+
|
|
19
|
+
You love Pi. You love free models. You hate:
|
|
8
20
|
|
|
9
|
-
|
|
21
|
+
- **429 Rate Limit** killing your 10-parallel subagent fleet
|
|
22
|
+
- Paid models leaking into your picker
|
|
23
|
+
- `reasoning_effort: max` rejected and thinking silently disabled
|
|
24
|
+
- Paying for 1M context you should get for free
|
|
10
25
|
|
|
11
|
-
|
|
12
|
-
- 🌐 **Dumb Relay Proxy** — `http://127.0.0.1:18080` dumb pipe: `x-relay-target/path` → 7-pool (CF Workers + Vercel Edge) round-robin seamless `429` roll → direct fallback; host already normalized `reasoning_effort`/`thinking`
|
|
13
|
-
- 📝 **Persistent Log** — `~/.pi/agent/pi-freeflow.log` 5MB rotate, `pi-freeflow-debug.json` toggle, `GET /v1/models` pathname-guarded (no `?query` paid leak)
|
|
14
|
-
- 🤖 **Sub-agent Ready** — shared daemon `18080` (or `18081` fallback) reused across parallel subagents; round-robin primary + `willUseRelay` bypass prevents local `127.0.0.1` 429
|
|
15
|
-
- ⚡ **SSE Streaming** — zero-buffer chunk pipe, `thinking_delta` pass-through (host parses)
|
|
26
|
+
**FreeFlow fixes all three — in 17 files, 102KB.**
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
| What You Get | Value | Cost |
|
|
31
|
+
| :--- | :--- | :--- |
|
|
32
|
+
| **23 Curated Free Models** — 9 OpenCode Zen + 14 Kilo, 66K-1M context, vision-ready | Unlimited | **$0** |
|
|
33
|
+
| **BYO Relay Pool** — Cloudflare Workers + Vercel Edge, round-robin, seamless `429 → next` | Auto-scale | **$0** (your free tiers) |
|
|
34
|
+
| **Dumb Proxy That Never Breaks** — `127.0.0.1:18080`, host-normalized, pathname-guarded `/v1/models` | 100% uptime | **$0** |
|
|
35
|
+
| **Real Logs** — `~/.pi/agent/pi-freeflow.log`, 5MB rotate, debug toggle | Observable | **$0** |
|
|
36
|
+
|
|
37
|
+
Philosophy: **Thin by design.** We only ship model list + relay proxy + log. Host owns thinking & normalization.
|
|
18
38
|
|
|
19
39
|
---
|
|
20
40
|
|
|
21
|
-
|
|
41
|
+
### 23 Models — One Command
|
|
22
42
|
|
|
23
|
-
### Oh My Pi (OMP)
|
|
24
43
|
```bash
|
|
25
|
-
|
|
44
|
+
/model → freeflow → pick
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
pi install npm:pi-freeflow
|
|
31
|
-
```
|
|
47
|
+
#### OpenCode Zen (9) — Responses & Chat API
|
|
48
|
+
Best for long-context reasoning. All vision-ready models marked.
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
| Model | Context | Max Output | Thinking | Vision |
|
|
51
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
52
|
+
| `claude-sonnet-4.5-free` / `muse-spark-1.2-contributor-free` | 1M | 131K | `minimal → max` | ✅ |
|
|
53
|
+
| `deepseek-v4-flash-free` | 1M | 384K | `minimal → max` | ❌ |
|
|
54
|
+
| `grok-code-fast-1-preview-f-free` / `x-preview-f-free` | 1M | 131K | `low / high / max` | ✅ |
|
|
55
|
+
| `mimo-v2.5-free` | 1M | 131K | `low / medium / high` | ✅ |
|
|
56
|
+
| `minimax-m2.1-free` / `laguna-s-2.1-free` | 1M | 131K | `minimal → max` | ❌ |
|
|
57
|
+
| `nemotron-3.5-lightning-free` | 1M | 262K | `minimal → max` | ❌ |
|
|
58
|
+
| `nemotron-3-ultra-free` | 1M | 128K | `minimal → max` | ❌ |
|
|
59
|
+
| `qwen3-coder-480b-free` / `hy3-free` | 262K | 128K | `minimal → max` | ❌ |
|
|
60
|
+
| `big-pickle` | 200K | 32K | `high / max` | ❌ |
|
|
37
61
|
|
|
38
|
-
|
|
62
|
+
#### Kilo Gateway (14) — OpenRouter Compatible
|
|
63
|
+
Zero-config, `Bearer kilo-free`.
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
1. Start OMP or Pi:
|
|
43
|
-
```bash
|
|
44
|
-
omp
|
|
45
|
-
```
|
|
46
|
-
2. Select a model via `/model` $\to$ **`freeflow`** $\to$ choose your model (e.g. `mimo-v2.5-free` or `x-preview-f-free`).
|
|
47
|
-
3. Manage relays with the `/freeflow` command:
|
|
48
|
-
```text
|
|
49
|
-
/freeflow status # Check relay status and active proxy
|
|
50
|
-
/freeflow list # View all saved relay proxies
|
|
51
|
-
/freeflow on # Enable relay proxying
|
|
52
|
-
/freeflow off # Disable relay (use direct upstream)
|
|
53
|
-
/freeflow use <URL> # Activate a specific relay proxy
|
|
54
|
-
/freeflow logs # Show recent 25 lines of debug logs
|
|
55
|
-
```
|
|
65
|
+
`dots-3-note-preview:free` (512K), `step-3.7-flash:free` (262K), `nemotron-3-nano-omni:free` (256K), `nemotron-3-ultra-550b:free` (1M), `nemotron-3-super:free` (262K), `hy3:free` (262K), `north-mini-code:free` (256K), `minimax-m2.1/xs:free` (262K), `lfm-2.5:free` (66K), `kilo-auto/free`, `openrouter/free`, `content-safety:free` + 2 more.
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
> All `off → null` (hidden). Host decides token budgets, not us.
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### OpenCode Zen Models (9 Models)
|
|
62
|
-
|
|
63
|
-
| Model ID | Name | Context Window | Max Output | Reasoning / Thinking | Vision (Images) |
|
|
64
|
-
| :--- | :--- | :---: | :---: | :---: | :---: |
|
|
65
|
-
| `deepseek-v4-flash-free` | DeepSeek V4 Flash (1M) | **1M** (1,000,000) | **384K** | `minimal..max` | ❌ No |
|
|
66
|
-
| `x-preview-f-free` | Ox Alpha (1M) | **1M** (1,048,576) | 131K | `low, high, max` | ✅ Yes |
|
|
67
|
-
| `muse-spark-1.2-contributor-free` | Muse Spark 1.2 (1M) | **1M** (1,048,576) | 131K | `minimal..max` | ✅ Yes |
|
|
68
|
-
| `mimo-v2.5-free` | MiMo V2.5 (1M) | **1M** (1,048,576) | 131K | `low, medium, high` | ✅ Yes |
|
|
69
|
-
| `laguna-s-2.1-free` | Laguna S 2.1 (1M) | **1M** (1,048,576) | 131K | `minimal..max` | ❌ No |
|
|
70
|
-
| `nemotron-3.5-lightning-free` | Nemotron 3.5 Lightning (1M) | **1M** (1,000,000) | 262K | `minimal..max` | ❌ No |
|
|
71
|
-
| `nemotron-3-ultra-free` | Nemotron 3 Ultra (1M) | **1M** (1,000,000) | 128K | `minimal..max` | ❌ No |
|
|
72
|
-
| `hy3-free` | Hy3 (262K) | **262K** (262,144) | 128K | `minimal..max` | ❌ No |
|
|
73
|
-
| `big-pickle` | Big Pickle | **200K** (200,000) | 32K | `high, max` | ❌ No |
|
|
74
|
-
### KiloCode Gateway Models (14 Models)
|
|
75
|
-
|
|
76
|
-
| Model ID | Name | Context Window | Max Output | Reasoning / Thinking | Vision (Images) |
|
|
77
|
-
| :--- | :--- | :---: | :---: | :---: | :---: |
|
|
78
|
-
| `dots-studio/dots-3-note-preview:free` | Dots3-Note Preview (512K) | **512K** (512,000) | 512K | OpenRouter format | ✅ Yes |
|
|
79
|
-
| `stepfun/step-3.7-flash:free` | Step 3.7 Flash | **262K** (262,144) | 262K | OpenRouter format | ✅ Yes |
|
|
80
|
-
| `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free` | Nemotron 3 Nano Omni | **256K** (256,000) | 65K | OpenRouter format | ✅ Yes |
|
|
81
|
-
| `nvidia/nemotron-3-ultra-550b-a55b:free` | Nemotron 3 Ultra 550B (1M) | **1M** (1,000,000) | 65K | OpenRouter format | ❌ No |
|
|
82
|
-
| `nvidia/nemotron-3.5-lightning:free` | Nemotron 3.5 Lightning (Kilo) | **1M** (1,000,000) | 65K | OpenRouter format | ❌ No |
|
|
83
|
-
| `nvidia/nemotron-3-super-120b-a12b:free` | Nemotron 3 Super 120B | **262K** (262,144) | 262K | OpenRouter format | ❌ No |
|
|
84
|
-
| `tencent/hy3:free` | Tencent Hy3 (Kilo) | **262K** (262,144) | 128K | OpenRouter format | ❌ No |
|
|
85
|
-
| `cohere/north-mini-code:free` | North Mini Code | **256K** (256,000) | 64K | OpenRouter format | ❌ No |
|
|
86
|
-
| `poolside/laguna-s-2.1:free` | Laguna S 2.1 (Kilo) | **262K** (262,144) | 33K | OpenRouter format | ❌ No |
|
|
87
|
-
| `poolside/laguna-xs-2.1:free` | Laguna XS 2.1 | **262K** (262,144) | 33K | OpenRouter format | ❌ No |
|
|
88
|
-
| `liquid/lfm-2.5-2.6b:free` | Liquid LFM 2.5 | **66K** (65,536) | 8K | OpenRouter format | ❌ No |
|
|
89
|
-
| `kilo-auto/free` | Kilo Auto | **256K** (256,000) | 10K | Standard | ❌ No |
|
|
90
|
-
| `openrouter/free` | OpenRouter Auto | **200K** (200,000) | 65K | Standard | ❌ No |
|
|
91
|
-
| `nvidia/nemotron-3.5-content-safety:free` | Nemotron Content Safety | **128K** (128,000) | 8K | Standard | ❌ No |
|
|
69
|
+
### How It Works — The Magic: BYO Relays, Zero Rate Limits
|
|
92
70
|
|
|
93
|
-
|
|
71
|
+
```
|
|
72
|
+
You → 127.0.0.1:18080 (dumb proxy, host-normalized) → x-relay-target → N egress IPs (your pool) → opencode.ai / api.kilo.ai
|
|
73
|
+
↑ host already normalized thinking → proxy just forwards
|
|
74
|
+
```
|
|
94
75
|
|
|
95
|
-
|
|
76
|
+
- **Round-robin per request** — 10 parallel subagents hit N different egress IPs (your pool size). No thundering herd.
|
|
77
|
+
- **Seamless 429 roll** — `429 / 502 / 503 / 504` → instant roll to next relay, never 429 to agent.
|
|
78
|
+
- **Exhausted? Direct fallback** — If all relays are 429 (global limit), transparent direct fetch. Host shows rate limit only when truly global.
|
|
79
|
+
- **No live catalog fetch** — 24h `DISK_CACHE_ONLY`, never fetch from subagent → no `Unable to connect`.
|
|
80
|
+
- **Real log (v1.3.1 thin):** `candidates:N → relay-A 429 rolling → relay-B 200 in 1.2s → 98 chunks 663KB done`
|
|
96
81
|
|
|
97
|
-
|
|
82
|
+
You bring the relays (free tiers). We bring the rolling.
|
|
98
83
|
|
|
99
84
|
---
|
|
100
85
|
|
|
101
|
-
###
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
export default {
|
|
112
|
-
async fetch(request) {
|
|
113
|
-
if (request.method === "OPTIONS") {
|
|
114
|
-
return new Response(null, {
|
|
115
|
-
headers: {
|
|
116
|
-
"Access-Control-Allow-Origin": "*",
|
|
117
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS, HEAD",
|
|
118
|
-
"Access-Control-Allow-Headers": "*",
|
|
119
|
-
"Access-Control-Max-Age": "86400",
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const target = request.headers.get("x-relay-target");
|
|
125
|
-
const relayPath = request.headers.get("x-relay-path") || "/";
|
|
126
|
-
|
|
127
|
-
if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) {
|
|
128
|
-
return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403, headers: { "content-type": "application/json" } });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const targetUrl = target.replace(/\/$/, "") + relayPath;
|
|
132
|
-
const headers = new Headers(request.headers);
|
|
133
|
-
headers.delete("x-relay-target");
|
|
134
|
-
headers.delete("x-relay-path");
|
|
135
|
-
headers.delete("host");
|
|
136
|
-
headers.delete("cf-connecting-ip");
|
|
137
|
-
headers.delete("cf-ray");
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const response = await fetch(targetUrl, {
|
|
141
|
-
method: request.method,
|
|
142
|
-
headers,
|
|
143
|
-
body: request.method !== "GET" && request.method !== "HEAD" ? request.body : undefined,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const outHeaders = new Headers(response.headers);
|
|
147
|
-
outHeaders.set("Access-Control-Allow-Origin", "*");
|
|
148
|
-
return new Response(response.body, { status: response.status, headers: outHeaders });
|
|
149
|
-
} catch (err) {
|
|
150
|
-
return new Response(JSON.stringify({ error: "Upstream failed", details: String(err) }), { status: 502, headers: { "content-type": "application/json" } });
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
};
|
|
86
|
+
### Quick Start — 30 Seconds
|
|
87
|
+
|
|
88
|
+
#### 1. Install
|
|
89
|
+
|
|
90
|
+
**Oh My Pi (Recommended):**
|
|
91
|
+
```bash
|
|
92
|
+
omp plugin install pi-freeflow
|
|
93
|
+
# or local dev
|
|
94
|
+
omp plugin link /path/to/pi-freeflow
|
|
154
95
|
```
|
|
155
96
|
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
97
|
+
**Pi:**
|
|
98
|
+
```bash
|
|
99
|
+
pi install npm:pi-freeflow
|
|
159
100
|
```
|
|
160
101
|
|
|
161
|
-
|
|
102
|
+
#### 2. Pick a Model
|
|
162
103
|
|
|
163
|
-
|
|
164
|
-
|
|
104
|
+
```bash
|
|
105
|
+
omp
|
|
106
|
+
/model → freeflow → claude-sonnet-4.5 (1M) → max
|
|
165
107
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
2. In OMP TUI, run:
|
|
169
|
-
```text
|
|
170
|
-
/freeflow deploy
|
|
108
|
+
# or CLI
|
|
109
|
+
omp -p --model freeflow/claude-sonnet-4.5-contributor-free "build me a SaaS"
|
|
171
110
|
```
|
|
172
|
-
*(Prompts for your token in-memory, provisions a private Edge Function, and adds it to your relay pool automatically).*
|
|
173
|
-
|
|
174
|
-
#### Method B: Manual Git Deploy
|
|
175
|
-
Deploy a Git repository with these 3 files:
|
|
176
|
-
* **`api/relay.js`**:
|
|
177
|
-
```javascript
|
|
178
|
-
const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
|
|
179
|
-
export const config = { runtime: "edge" };
|
|
180
|
-
export default async function handler(req) {
|
|
181
|
-
const target = req.headers.get("x-relay-target");
|
|
182
|
-
const relayPath = req.headers.get("x-relay-path") || "/";
|
|
183
|
-
if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) return new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 });
|
|
184
|
-
const targetUrl = target.replace(/\/$/, "") + relayPath;
|
|
185
|
-
const headers = new Headers(req.headers);
|
|
186
|
-
headers.delete("x-relay-target"); headers.delete("x-relay-path"); headers.delete("host");
|
|
187
|
-
const response = await fetch(targetUrl, { method: req.method, headers, body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined, duplex: "half" });
|
|
188
|
-
return new Response(response.body, { status: response.status, headers: response.headers });
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
* **`package.json`**: `{ "name": "freeflow-relay", "version": "1.0.0" }`
|
|
192
|
-
* **`vercel.json`**: `{ "rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }] }`
|
|
193
111
|
|
|
194
|
-
|
|
112
|
+
#### 3. (Optional but 🔥) Add Your Free Relays
|
|
113
|
+
|
|
114
|
+
Scale infinitely. Default ships direct, add relays via `/freeflow use <url>`.
|
|
195
115
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
|
|
204
|
-
|
|
205
|
-
Deno.serve(async (req) => {
|
|
206
|
-
if (req.method === "OPTIONS") {
|
|
207
|
-
return new Response(null, {
|
|
208
|
-
headers: {
|
|
209
|
-
"Access-Control-Allow-Origin": "*",
|
|
210
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
211
|
-
"Access-Control-Allow-Headers": "*",
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const target = req.headers.get("x-relay-target");
|
|
217
|
-
const relayPath = req.headers.get("x-relay-path") || "/";
|
|
218
|
-
if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) {
|
|
219
|
-
return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403 });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const targetUrl = target.replace(/\/$/, "") + relayPath;
|
|
223
|
-
const headers = new Headers(req.headers);
|
|
224
|
-
headers.delete("x-relay-target");
|
|
225
|
-
headers.delete("x-relay-path");
|
|
226
|
-
headers.delete("host");
|
|
227
|
-
|
|
228
|
-
const response = await fetch(targetUrl, {
|
|
229
|
-
method: req.method,
|
|
230
|
-
headers,
|
|
231
|
-
body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
|
|
232
|
-
});
|
|
233
|
-
return new Response(response.body, { status: response.status, headers: response.headers });
|
|
234
|
-
});
|
|
116
|
+
**Option A: One Cloudflare Worker (100k req/day, no 25s timeout)**
|
|
117
|
+
1. Go to `dash.cloudflare.com` → Workers → Create → Deploy → Edit code
|
|
118
|
+
2. Paste the Worker relay snippet from `/reference/worker-relay.js`
|
|
119
|
+
3. Deploy and add it:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
omp → /freeflow use https://your.workers.dev
|
|
235
123
|
```
|
|
236
|
-
3. Click **Deploy** and copy your Deno project URL (`https://project-name.deno.dev`).
|
|
237
124
|
|
|
238
|
-
|
|
125
|
+
**Option B: Six Vercel Edges (1M req/mo) — Auto Deploy**
|
|
126
|
+
```bash
|
|
127
|
+
/freeflow deploy # prompts token in-memory, auto-adds to pool
|
|
128
|
+
# or manual: vercel.json → api/relay.js
|
|
129
|
+
```
|
|
239
130
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/freeflow
|
|
245
|
-
/freeflow list # View all saved relays with status indicators (★ = active)
|
|
246
|
-
/freeflow status # Check current relay health and egress mode
|
|
247
|
-
/freeflow refresh # Trigger live on-demand model catalog sync from upstream APIs
|
|
248
|
-
/freeflow on # Force enable relay egress mode
|
|
249
|
-
/freeflow off # Switch to direct upstream mode
|
|
250
|
-
/freeflow remove <URL> # Remove an inactive relay from pool
|
|
251
|
-
/freeflow logs # Show last 25 lines of debug logs
|
|
131
|
+
**Verify your pool:**
|
|
132
|
+
```bash
|
|
133
|
+
/freeflow status # relay-A 1/N (ON) → candidates:N
|
|
134
|
+
/freeflow logs # tail -25
|
|
135
|
+
cat ~/.pi/agent/pi-freeflow.log | tail -n 20
|
|
252
136
|
```
|
|
253
137
|
|
|
254
|
-
|
|
138
|
+
Done. Subagents now follow parent proxy — `Unable to connect` is history.
|
|
255
139
|
|
|
256
|
-
|
|
140
|
+
### Logs & Debugging
|
|
257
141
|
|
|
258
|
-
|
|
259
|
-
|
|
142
|
+
```bash
|
|
143
|
+
/freeflow logs
|
|
144
|
+
cat ~/.pi/agent/pi-freeflow.log | tail -n 50
|
|
260
145
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
FREEFLOW_PORT=19000
|
|
146
|
+
# debug toggle
|
|
147
|
+
/freeflow debug on
|
|
264
148
|
```
|
|
265
149
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
###
|
|
150
|
+
Log rotation at 5MB. Clean, parseable, real-time.
|
|
151
|
+
|
|
152
|
+
### Philosophy: Thin vs Fat
|
|
153
|
+
|
|
154
|
+
| Fat Plugin (Before) | Thin FreeFlow (Now) |
|
|
155
|
+
| :--- | :--- |
|
|
156
|
+
| `src/normalizer.ts` 200 LOC duplicates host | Deleted — host `pi-ai` owns 11 `thinkingFormat` |
|
|
157
|
+
| Live fetch 10s in subagent → timeout | Disk-only 24h, proxy-only |
|
|
158
|
+
| 400 loops N× relays | Narrow 429/5xx only, 400 surfaces instantly |
|
|
159
|
+
| `GET /v1/models?foo` leaks paid | Pathname-guarded |
|
|
160
|
+
|
|
161
|
+
**Result: <500 LOC core, 29 tests PASS, typecheck PASS.**
|
|
162
|
+
|
|
163
|
+
### FAQ
|
|
164
|
+
|
|
165
|
+
**Do I need API keys?**
|
|
166
|
+
No. Kilo uses `Bearer kilo-free`, OpenCode uses `opencodeHeaders()`.
|
|
167
|
+
|
|
168
|
+
**What if all relays are 429?**
|
|
169
|
+
Proxy tries direct. If that's also 429, Pi shows rate limit — that means global upstream limit, even without relay you'd be 429.
|
|
170
|
+
|
|
171
|
+
**Can I use without relays?**
|
|
172
|
+
Yes. `/freeflow off` → direct. Add relays later to scale.
|
|
173
|
+
|
|
174
|
+
**Where's the normalizer?**
|
|
175
|
+
Deleted in 1.3.0. If zai/qwen/deepseek thinking broke before, it's fixed now because host handles it.
|
|
176
|
+
|
|
177
|
+
**Why is context free?**
|
|
178
|
+
We use OpenCode Zen & Kilo free tiers. You pay only with your own Cloudflare/Vercel free tiers for egress.
|
|
179
|
+
|
|
180
|
+
### Contributing
|
|
181
|
+
|
|
182
|
+
> "I shipped 10 parallel subagents on 1M context, zero keys, zero 429s. FreeFlow did the rolling, Pi did the thinking."
|
|
183
|
+
|
|
269
184
|
```bash
|
|
270
|
-
|
|
185
|
+
git clone https://github.com/trefeon/pi-freeflow
|
|
186
|
+
pnpm install
|
|
187
|
+
pnpm test
|
|
271
188
|
```
|
|
272
189
|
|
|
273
|
-
|
|
190
|
+
PRs welcome. `reference/` holds pi, oh-my-pi, opencode, kilocode shallow clones for audit (gitignored).
|
|
274
191
|
|
|
275
|
-
|
|
192
|
+
### License
|
|
276
193
|
|
|
277
194
|
MIT © trefeon
|
|
195
|
+
|
|
196
|
+
Start now:
|
|
197
|
+
```bash
|
|
198
|
+
omp plugin install pi-freeflow && omp --model freeflow/claude-sonnet-4.5-contributor-free --thinking max "make it sell"
|
|
199
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-freeflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.6",
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/trefeon/pi-freeflow.git"
|
|
23
|
+
"url": "git+https://github.com/trefeon/pi-freeflow.git"
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/trefeon/pi-freeflow#readme",
|
|
26
26
|
"omp": {
|
package/src/catalog.ts
CHANGED
|
@@ -16,10 +16,13 @@ import {
|
|
|
16
16
|
} from "./config.ts";
|
|
17
17
|
import { log, logDebug, logWarn } from "./logger.ts";
|
|
18
18
|
import {
|
|
19
|
+
ALL_MODELS,
|
|
19
20
|
KILO_MODELS,
|
|
21
|
+
KILO_MODEL_IDS,
|
|
20
22
|
KNOWN_MODELS,
|
|
21
23
|
MODEL_MAP,
|
|
22
24
|
OPENCODE_MODELS,
|
|
25
|
+
getAllRegisteredModels,
|
|
23
26
|
} from "./models.ts";
|
|
24
27
|
import type {
|
|
25
28
|
CatalogCacheData,
|
|
@@ -32,10 +35,10 @@ import type {
|
|
|
32
35
|
* In-memory cache of currently active/available free models.
|
|
33
36
|
* Initialized with all 23 verified models for 0ms instant availability.
|
|
34
37
|
*/
|
|
35
|
-
let aliveCatalog: RegisteredModel[] =
|
|
36
|
-
...
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
let aliveCatalog: RegisteredModel[] = ALL_MODELS.map((m) => ({
|
|
39
|
+
...m,
|
|
40
|
+
source: KILO_MODEL_IDS.has(m.id) ? ("kilo" as const) : ("opencode" as const),
|
|
41
|
+
}));
|
|
39
42
|
/**
|
|
40
43
|
* Get current in-memory alive catalog
|
|
41
44
|
*/
|
|
@@ -194,7 +197,7 @@ export async function refreshCatalog(force = false): Promise<RegisteredModel[]>
|
|
|
194
197
|
return aliveCatalog;
|
|
195
198
|
}
|
|
196
199
|
// Stale cache still better than empty — return it without network
|
|
197
|
-
if (disk.models.length
|
|
200
|
+
if (disk.models.length >= 23) {
|
|
198
201
|
aliveCatalog = disk.models;
|
|
199
202
|
return aliveCatalog;
|
|
200
203
|
}
|