pi-freeflow 1.1.4 โ†’ 1.1.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 CHANGED
@@ -94,10 +94,18 @@ omp plugin link /path/to/pi-freeflow
94
94
 
95
95
  ---
96
96
 
97
- ## ๐ŸŒ Setting Up Your Own Egress Relays
97
+ ## ๐ŸŒ Setting Up Your Own Egress Relays (Multi-Cloud)
98
98
 
99
- ### 1. Cloudflare Worker Relay (Recommended)
100
- Deploy this script to Cloudflare Workers (100,000 free requests/day):
99
+ By default, FreeFlow works out of the box with direct upstream calls. To expand concurrency, bypass IP rate limits, and enable rolling failover, you can connect your own free cloud relays across **Cloudflare Workers**, **Vercel Edge**, **Deno Deploy**, or a **Linux VPS**.
100
+
101
+ ---
102
+
103
+ ### 1. Cloudflare Worker Relay (Recommended โญ)
104
+ > **Free Tier**: 100,000 requests/day ยท Low latency ยท No 25-second execution timeout.
105
+
106
+ 1. Go to [dash.cloudflare.com](https://dash.cloudflare.com) $\to$ **Workers & Pages** $\to$ **Create application** $\to$ **Create Worker**.
107
+ 2. Name your worker (e.g. `my-freeflow-relay`) $\to$ click **Deploy**.
108
+ 3. Click **Edit code**, paste this JavaScript, and click **Save and Deploy**:
101
109
 
102
110
  ```javascript
103
111
  const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
@@ -108,8 +116,9 @@ export default {
108
116
  return new Response(null, {
109
117
  headers: {
110
118
  "Access-Control-Allow-Origin": "*",
111
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
119
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS, HEAD",
112
120
  "Access-Control-Allow-Headers": "*",
121
+ "Access-Control-Max-Age": "86400",
113
122
  },
114
123
  });
115
124
  }
@@ -117,8 +126,8 @@ export default {
117
126
  const target = request.headers.get("x-relay-target");
118
127
  const relayPath = request.headers.get("x-relay-path") || "/";
119
128
 
120
- if (!target || !ALLOWED_TARGETS.includes(target)) {
121
- return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403 });
129
+ if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) {
130
+ return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403, headers: { "content-type": "application/json" } });
122
131
  }
123
132
 
124
133
  const targetUrl = target.replace(/\/$/, "") + relayPath;
@@ -126,42 +135,147 @@ export default {
126
135
  headers.delete("x-relay-target");
127
136
  headers.delete("x-relay-path");
128
137
  headers.delete("host");
138
+ headers.delete("cf-connecting-ip");
139
+ headers.delete("cf-ray");
140
+
141
+ try {
142
+ const response = await fetch(targetUrl, {
143
+ method: request.method,
144
+ headers,
145
+ body: request.method !== "GET" && request.method !== "HEAD" ? request.body : undefined,
146
+ });
129
147
 
130
- const response = await fetch(targetUrl, {
131
- method: request.method,
132
- headers,
133
- body: request.method !== "GET" && request.method !== "HEAD" ? request.body : undefined,
134
- });
148
+ const outHeaders = new Headers(response.headers);
149
+ outHeaders.set("Access-Control-Allow-Origin", "*");
150
+ return new Response(response.body, { status: response.status, headers: outHeaders });
151
+ } catch (err) {
152
+ return new Response(JSON.stringify({ error: "Upstream failed", details: String(err) }), { status: 502, headers: { "content-type": "application/json" } });
153
+ }
154
+ },
155
+ };
156
+ ```
135
157
 
136
- return new Response(response.body, {
137
- status: response.status,
138
- headers: response.headers,
139
- });
140
- ### 2. Vercel Relay
141
- Deploy using the built-in deploy command:
158
+ 4. Copy your Worker URL and register it inside OMP:
159
+ ```text
160
+ /freeflow use https://my-freeflow-relay.your-subdomain.workers.dev
161
+ ```
162
+
163
+ ---
164
+
165
+ ### 2. Vercel Edge Relay
166
+ > **Free Tier**: 1,000,000 requests/month.
167
+
168
+ #### Method A: Automated In-Memory CLI Deploy
169
+ 1. Generate a Vercel token at [vercel.com/account/tokens](https://vercel.com/account/tokens).
170
+ 2. In OMP TUI, run:
142
171
  ```text
143
172
  /freeflow deploy
144
173
  ```
145
- *(Prompts for your Vercel API token in-memory and automatically creates a private Edge Function relay).*
174
+ *(Prompts for your token in-memory, provisions a private Edge Function, and adds it to your relay pool automatically).*
175
+
176
+ #### Method B: Manual Git Deploy
177
+ Deploy a Git repository with these 3 files:
178
+ * **`api/relay.js`**:
179
+ ```javascript
180
+ const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
181
+ export const config = { runtime: "edge" };
182
+ export default async function handler(req) {
183
+ const target = req.headers.get("x-relay-target");
184
+ const relayPath = req.headers.get("x-relay-path") || "/";
185
+ if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) return new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 });
186
+ const targetUrl = target.replace(/\/$/, "") + relayPath;
187
+ const headers = new Headers(req.headers);
188
+ headers.delete("x-relay-target"); headers.delete("x-relay-path"); headers.delete("host");
189
+ const response = await fetch(targetUrl, { method: req.method, headers, body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined, duplex: "half" });
190
+ return new Response(response.body, { status: response.status, headers: response.headers });
191
+ }
192
+ ```
193
+ * **`package.json`**: `{ "name": "freeflow-relay", "version": "1.0.0" }`
194
+ * **`vercel.json`**: `{ "rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }] }`
195
+
196
+ ---
197
+
198
+ ### 3. Deno Deploy Relay
199
+ > **Free Tier**: 100,000 requests/day.
200
+
201
+ 1. Open [dash.deno.com](https://dash.deno.com) $\to$ **New Project** $\to$ **Playground**.
202
+ 2. Paste the following TypeScript code:
203
+
204
+ ```typescript
205
+ const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
206
+
207
+ Deno.serve(async (req) => {
208
+ if (req.method === "OPTIONS") {
209
+ return new Response(null, {
210
+ headers: {
211
+ "Access-Control-Allow-Origin": "*",
212
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
213
+ "Access-Control-Allow-Headers": "*",
214
+ },
215
+ });
216
+ }
217
+
218
+ const target = req.headers.get("x-relay-target");
219
+ const relayPath = req.headers.get("x-relay-path") || "/";
220
+ if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) {
221
+ return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403 });
222
+ }
223
+
224
+ const targetUrl = target.replace(/\/$/, "") + relayPath;
225
+ const headers = new Headers(req.headers);
226
+ headers.delete("x-relay-target");
227
+ headers.delete("x-relay-path");
228
+ headers.delete("host");
229
+
230
+ const response = await fetch(targetUrl, {
231
+ method: req.method,
232
+ headers,
233
+ body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
234
+ });
235
+ return new Response(response.body, { status: response.status, headers: response.headers });
236
+ });
237
+ ```
238
+ 3. Click **Deploy** and copy your Deno project URL (`https://project-name.deno.dev`).
239
+
240
+ ---
241
+
242
+ ## ๐ŸŽฎ Managing Relays in FreeFlow
243
+
244
+ Inside the OMP / Pi TUI:
245
+ ```text
246
+ /freeflow use <URL> # Set active relay and add to saved pool
247
+ /freeflow list # View all saved relays with status indicators (โ˜… = active)
248
+ /freeflow status # Check current relay health and egress mode
249
+ /freeflow refresh # Trigger live on-demand model catalog sync from upstream APIs
250
+ /freeflow on # Force enable relay egress mode
251
+ /freeflow off # Switch to direct upstream mode
252
+ /freeflow remove <URL> # Remove an inactive relay from pool
253
+ /freeflow logs # Show last 25 lines of debug logs
254
+ ```
146
255
 
147
256
  ---
148
257
 
149
258
  ## ๐Ÿ› ๏ธ Diagnostics & Troubleshooting
150
259
 
151
260
  ### Custom Port Configuration
152
- By default, `pi-freeflow` uses a **Single Shared Port** (`18080`). All concurrent sub-agents automatically share the same master proxy port without spawning redundant servers.
261
+ By default, `pi-freeflow` uses a **Single Shared Master Port** (`18080`). All concurrent sub-agents automatically reuse this master proxy without spawning redundant servers or extra sockets.
153
262
 
154
263
  To change the base port:
155
264
  ```env
156
265
  FREEFLOW_PORT=19000
157
266
  ```
158
267
 
159
- View live debug logs:
268
+ ### Subagent Credentials
269
+ If subagents report missing provider keys, verify `~/.omp/agent/.env`:
270
+ ```env
271
+ FREEFLOW_API_KEY=freeflow
272
+ ```
273
+
274
+ ### Live Logs
160
275
  ```bash
161
- cat ~/.pi/agent/pi-freeflow.log | tail -n 30
162
- # or inside OMP:
163
- /freeflow logs
276
+ cat ~/.pi/agent/pi-freeflow.log | tail -n 40
164
277
  ```
278
+
165
279
  ---
166
280
 
167
281
  ## ๐Ÿ“„ License
@@ -1729,12 +1729,46 @@ export default async function (pi: ExtensionAPI) {
1729
1729
  pi.registerCommand("freeflow", commandSpec);
1730
1730
  pi.registerCommand("bansos", commandSpec);
1731
1731
 
1732
- // Reload persisted state on session start/resume (env overrides still win).
1732
+ function updateStatusBar(ui?: ExtensionContext["ui"], providerName?: string) {
1733
+ if (!ui) return;
1734
+ if (relayState.enabled && relayState.relays.length > 0) {
1735
+ const label = shortRelayLabel(relayState.url);
1736
+ const idx = Math.max(
1737
+ 1,
1738
+ relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
1739
+ );
1740
+ const total = relayState.relays.length;
1741
+ ui.setStatus?.("freeflow", `relay: ON | ${label} ${idx}/${total}`);
1742
+ } else {
1743
+ ui.setStatus?.("freeflow", undefined);
1744
+ }
1745
+ ui.setStatus?.("bansos", undefined);
1746
+ }
1747
+
1748
+ // Reload persisted state on session start/resume and show status if active
1733
1749
  pi.on?.("session_start", async (_event, ctx) => {
1734
1750
  relayState = resolveRelayState();
1735
1751
  statusUi = ctx.ui;
1736
- ctx.ui?.setStatus?.("freeflow", undefined);
1737
- ctx.ui?.setStatus?.("bansos", undefined);
1752
+ updateStatusBar(ctx.ui);
1753
+ });
1754
+
1755
+ // Update status bar immediately when user switches models
1756
+ pi.on?.("model_select", async (event, ctx) => {
1757
+ statusUi = ctx.ui;
1758
+ const model =
1759
+ event && typeof event === "object" && "model" in event
1760
+ ? event.model
1761
+ : null;
1762
+ const provider =
1763
+ model && typeof model === "object" && "provider" in model
1764
+ ? String(model.provider)
1765
+ : null;
1766
+ if (provider === "freeflow") {
1767
+ updateStatusBar(ctx.ui, "freeflow");
1768
+ } else if (provider) {
1769
+ // Clear status when switched to other providers (e.g. anthropic, openai)
1770
+ ctx.ui?.setStatus?.("freeflow", undefined);
1771
+ }
1738
1772
  });
1739
1773
 
1740
1774
  // Pi normally pauses after threshold compaction. Queue a follow-up while the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.1.4",
4
+ "version": "1.1.6",
5
5
  "description": "Personal multi-cloud rolling fallback relay for OpenCode Zen and KiloCode models in OMP/Pi",
6
6
  "keywords": [
7
7
  "pi-package",