pi-freeflow 1.4.9 → 1.4.11

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
@@ -25,7 +25,7 @@ Join devs bypassing rate limits with their own relay pools. BYO, add as many as
25
25
  | **Smart Model Aliasing** | Clean slash-free & colon-free CLI model names compatible with thinking selectors | DX Optimized | **$0** |
26
26
  | **Auto-Enabled on Session** | Relay stays enabled in `auto` mode on session start and model switch | Zero Friction | **$0** |
27
27
  | **Interactive CLI Management** | 10+ `/freeflow` subcommands (`status`, `list`, `use`, `add`, `label`, `remove`, `deploy`, `logs`, `debug`) | Full Control | **$0** |
28
- | **Dumb Proxy That Never Breaks** | `127.0.0.1:18080`, host-normalized, pathname-guarded `/v1/models` | 100% Uptime | **$0** |
28
+ | **Dumb Proxy That Never Breaks** | `127.0.0.1:28180`, host-normalized, pathname-guarded `/v1/models` | 100% Uptime | **$0** |
29
29
  | **Observable Real Logs** | `~/.pi/agent/pi-freeflow.log`, 5MB auto-rotation, real-time debug toggle | Observable | **$0** |
30
30
 
31
31
  Philosophy: **Thin by design.** We only ship model list + relay proxy + log. Host owns thinking & normalization.
@@ -78,7 +78,7 @@ Keyless access with `Bearer kilo-free`. Clean slash-free and colon-free CLI alia
78
78
  ### How It Works: BYO Relays, Zero Rate Limits
79
79
 
80
80
  ```
81
- You → 127.0.0.1:18080 (dumb proxy, host-normalized) → x-relay-target → N egress IPs (your pool) → opencode.ai / api.kilo.ai
81
+ You → 127.0.0.1:28180 (dumb proxy, host-normalized) → x-relay-target → N egress IPs (your pool) → opencode.ai / api.kilo.ai
82
82
  ↑ host already normalized thinking → proxy just forwards
83
83
  ```
84
84
 
@@ -176,6 +176,34 @@ export default {
176
176
  /freeflow deploy vercel # prompts token in-memory, auto-adds to pool
177
177
  # or shorthand: /freeflow deploy
178
178
  ```
179
+ *Manual fallback:* Push 2 files (`api/relay.js` + `vercel.json`) to GitHub $\to$ Import on `vercel.com` $\to$ `/freeflow add https://your.vercel.app vercel-relay-1`
180
+
181
+ ```js
182
+ // api/relay.js
183
+ const ALLOWED_TARGETS = ["https://opencode.ai", "https://api.kilo.ai"];
184
+ export const config = { runtime: "edge" };
185
+ export default async function handler(req) {
186
+ const target = req.headers.get("x-relay-target");
187
+ const relayPath = req.headers.get("x-relay-path") || "/";
188
+ if (!target || !ALLOWED_TARGETS.includes(target.replace(/\/$/, ""))) {
189
+ return new Response(JSON.stringify({ error: "Forbidden target" }), { status: 403 });
190
+ }
191
+ const headers = new Headers(req.headers);
192
+ headers.delete("x-relay-target"); headers.delete("x-relay-path"); headers.delete("host");
193
+ const res = await fetch(target.replace(/\/$/, "") + relayPath, {
194
+ method: req.method,
195
+ headers,
196
+ body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
197
+ duplex: "half",
198
+ });
199
+ return new Response(res.body, { status: res.status, headers: res.headers });
200
+ }
201
+ ```
202
+
203
+ ```json
204
+ // vercel.json
205
+ { "rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }] }
206
+ ```
179
207
 
180
208
  **Option C: Deno Deploy (100k req/day) — Auto Deploy**
181
209
  ```bash
@@ -279,7 +307,7 @@ src/
279
307
  ├── index.ts # extension entry, lifecycle hooks
280
308
  ├── models.ts # 21-model catalog definitions
281
309
  ├── catalog.ts # model catalog cache (24h disk)
282
- ├── proxy.ts # local proxy server (127.0.0.1:18080)
310
+ ├── proxy.ts # local proxy server (127.0.0.1:28180)
283
311
  ├── relay.ts # relay selection & round-robin
284
312
  ├── relay-state.ts # relay pool state, health tracking
285
313
  ├── rate-limiter.ts # adaptive cooldown on 429/504/socket errors
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "pi-freeflow",
3
- "type": "module",
4
- "version": "1.4.9",
5
- "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
- "main": "extensions/index.ts",
7
- "types": "src/index.ts",
8
- "keywords": [
9
- "pi-package",
10
- "pi-extension",
11
- "oh-my-pi",
12
- "omp",
13
- "free-models",
14
- "opencode",
15
- "kilocode",
16
- "ai-models",
17
- "relay"
18
- ],
19
- "author": "trefeon",
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/trefeon/pi-freeflow.git"
24
- },
25
- "homepage": "https://github.com/trefeon/pi-freeflow#readme",
26
- "engines": {
27
- "node": ">=22.6.0"
28
- },
29
- "omp": {
30
- "extensions": [
31
- "./extensions"
32
- ]
33
- },
34
- "pi": {
35
- "extensions": [
36
- "./extensions"
37
- ]
38
- },
39
- "files": [
40
- "extensions",
41
- "src",
42
- "README.md",
43
- "LICENSE"
44
- ],
45
- "scripts": {
46
- "test": "node --experimental-strip-types --test --test-concurrency=1 test/**/*.test.ts",
47
- "typecheck": "tsc --noEmit",
48
- "smoke": "node --experimental-strip-types -e \"import('./extensions/index.ts').then(() => console.log('✓ Smoke test passed: extensions/index.ts loaded successfully')).catch(err => { console.error(err); process.exit(1); })\""
49
- },
50
- "devDependencies": {
51
- "@earendil-works/pi-coding-agent": "^0.84.3",
52
- "@types/node": "^22.13.9",
53
- "typescript": "^5.8.2"
54
- }
55
- }
1
+ {
2
+ "name": "pi-freeflow",
3
+ "type": "module",
4
+ "version": "1.4.11",
5
+ "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
+ "main": "extensions/index.ts",
7
+ "types": "src/index.ts",
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi-extension",
11
+ "oh-my-pi",
12
+ "omp",
13
+ "free-models",
14
+ "opencode",
15
+ "kilocode",
16
+ "ai-models",
17
+ "relay"
18
+ ],
19
+ "author": "trefeon",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/trefeon/pi-freeflow.git"
24
+ },
25
+ "homepage": "https://github.com/trefeon/pi-freeflow#readme",
26
+ "engines": {
27
+ "node": ">=22.6.0"
28
+ },
29
+ "omp": {
30
+ "extensions": [
31
+ "./extensions"
32
+ ]
33
+ },
34
+ "pi": {
35
+ "extensions": [
36
+ "./extensions"
37
+ ]
38
+ },
39
+ "files": [
40
+ "extensions",
41
+ "src",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "scripts": {
46
+ "test": "node --experimental-strip-types --test --test-concurrency=1 test/**/*.test.ts",
47
+ "typecheck": "tsc --noEmit",
48
+ "smoke": "node --experimental-strip-types -e \"import('./extensions/index.ts').then(() => console.log('✓ Smoke test passed: extensions/index.ts loaded successfully')).catch(err => { console.error(err); process.exit(1); })\""
49
+ },
50
+ "devDependencies": {
51
+ "@earendil-works/pi-coding-agent": "^0.84.3",
52
+ "@types/node": "^22.13.9",
53
+ "typescript": "^5.8.2"
54
+ }
55
+ }
package/src/config.ts CHANGED
@@ -14,7 +14,8 @@ export const KILO_CHAT_URL = "https://api.kilo.ai/api/gateway/chat/completions";
14
14
  export const OPENCODE_API_URL = `${UPSTREAM_OPENCODE}/v1`;
15
15
 
16
16
  // ── Network & Server defaults ───────────────────────────────────────
17
- export const DEFAULT_PORT = 18080;
17
+ export const DEFAULT_PORT = 28180;
18
+ export const LEGACY_PORT = 18080;
18
19
  export const HOST = "127.0.0.1";
19
20
  export const DEFAULT_HOST = "127.0.0.1";
20
21
 
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * pi-freeflow — Modular, high-resiliency LLM extension for Pi & Oh My Pi (OMP)
3
3
  *
4
4
  * Provides access to 21 free models (7 OpenCode Zen + 14 KiloCode Gateway) with:
5
- * - Single-port daemon reuse on 18080 across concurrent subagents
5
+ * - Single-port daemon reuse on 28180 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
8
8
  * - Per-model thinking/reasoning translation and streaming SSE pass-through
@@ -17,7 +17,7 @@ import {
17
17
  setAliveCatalog,
18
18
  } from "./catalog.ts";
19
19
  import { createCommandSpec, updateStatusBar } from "./commands.ts";
20
- import { DEFAULT_HOST, HOST, PORT } from "./config.ts";
20
+ import { DEFAULT_HOST, HOST, LEGACY_PORT, PORT } from "./config.ts";
21
21
  import { log, logInfo, logWarn } from "./logger.ts";
22
22
  import { ALL_MODELS, KILO_MODEL_IDS, MODEL_MAP, getAllRegisteredModels, resolveCanonicalModelId } from "./models.ts";
23
23
  import { isProxyAlive, startProxy } from "./proxy.ts";
@@ -117,12 +117,20 @@ export default async function (pi: ExtensionAPI): Promise<void> {
117
117
  let actualPort = PORT;
118
118
 
119
119
  // 2. Single-Port Shared Pattern: Check if daemon is already running (e.g. parent session)
120
- const alreadyRunning = await isProxyAlive(PORT);
120
+ // Dual-probe: probe current PORT first; if missing, check LEGACY_PORT so existing v1.4.9
121
+ // sessions on 18080 are seamlessly reused without split-brain or duplicate daemons.
122
+ let alreadyRunning = await isProxyAlive(PORT);
121
123
  if (alreadyRunning) {
122
124
  logInfo(
123
125
  `Reusing existing pi-freeflow proxy daemon on http://${HOST}:${PORT}`,
124
126
  );
125
127
  actualPort = PORT;
128
+ } else if (PORT !== LEGACY_PORT && (await isProxyAlive(LEGACY_PORT))) {
129
+ logInfo(
130
+ `Reusing existing legacy pi-freeflow proxy daemon on http://${HOST}:${LEGACY_PORT}`,
131
+ );
132
+ alreadyRunning = true;
133
+ actualPort = LEGACY_PORT;
126
134
  } else {
127
135
  try {
128
136
  const r = await startProxy();
@@ -161,6 +169,19 @@ export default async function (pi: ExtensionAPI): Promise<void> {
161
169
  ensuringDaemon = true;
162
170
  try {
163
171
  if (await isProxyAlive(actualPort)) return;
172
+ // If actualPort died, check if another daemon is alive on PORT or LEGACY_PORT before binding
173
+ if (await isProxyAlive(PORT)) {
174
+ actualPort = PORT;
175
+ registerCatalog(getAliveCatalog());
176
+ logInfo(`Re-attached to proxy daemon on http://${HOST}:${PORT}`);
177
+ return;
178
+ }
179
+ if (PORT !== LEGACY_PORT && (await isProxyAlive(LEGACY_PORT))) {
180
+ actualPort = LEGACY_PORT;
181
+ registerCatalog(getAliveCatalog());
182
+ logInfo(`Re-attached to legacy proxy daemon on http://${HOST}:${LEGACY_PORT}`);
183
+ return;
184
+ }
164
185
  const r = await startProxy();
165
186
  if (r.server) server = r.server;
166
187
  if (r.port) actualPort = r.port;
package/src/proxy.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Single-port local HTTP proxy and dynamic upstream router for pi-freeflow
3
3
  *
4
- * Provides loopback proxying on port 18080 (shared across parent and subagents),
4
+ * Provides loopback proxying on port 28180 (shared across parent and subagents),
5
5
  * intelligent routing to OpenCode Zen and KiloCode Gateway, and failover support.
6
6
  */
7
7
 
@@ -111,9 +111,8 @@ export async function isProxyAlive(port: number): Promise<boolean> {
111
111
 
112
112
  /**
113
113
  * Start the local HTTP proxy daemon.
114
- *
115
- * Implements master/worker single-port reuse: if port 18080 is already held by a live
116
- * parent or sibling OMP session, resolves immediately with { server: null, port: 18080 }.
114
+ * Implements master/worker single-port reuse: if port 28180 is already held by a live
115
+ * parent or sibling OMP session, resolves immediately with { server: null, port: 28180 }.
117
116
  */
118
117
  export function startProxy(
119
118
  overridePort?: number,
@@ -175,7 +175,8 @@ export function pipeUpstreamStream(
175
175
  if (!res.headersSent) {
176
176
  res.writeHead(502, { "content-type": "application/json" });
177
177
  } else {
178
- ensureTerminalEvent(true, errorMsg);
178
+ const isSubstantial = totalChunks > 50 && totalBytes > 100 * 1024;
179
+ ensureTerminalEvent(!isSubstantial, errorMsg, true);
179
180
  }
180
181
  if (!res.writableEnded) {
181
182
  res.end();