pi-freeflow 1.1.8 → 1.2.0

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.
Files changed (2) hide show
  1. package/extensions/index.ts +72 -41
  2. package/package.json +1 -1
@@ -735,13 +735,37 @@ const LOG_MAX_BYTES = 5 * 1024 * 1024;
735
735
  const LOG_MAX_FILES = 3;
736
736
  const DEBUG_STATE_FILE = path.join(homedir(), ".pi", "agent", "pi-freeflow-debug.json");
737
737
  interface DebugState { debug: boolean; level?: LogLevel }
738
+ let cachedDebugState: DebugState | null | undefined = undefined;
739
+ let cachedDebugMtime = 0;
740
+ let cachedDebugAt = 0;
738
741
  function loadDebugState(): DebugState | null {
742
+ const now = Date.now();
743
+ // cache for 1s to avoid per-chunk FS hit in hot pipe path
744
+ if (cachedDebugState !== undefined && now - cachedDebugAt < 1000) {
745
+ return cachedDebugState;
746
+ }
739
747
  try {
740
- if (!fs.existsSync(DEBUG_STATE_FILE)) return null;
748
+ if (!fs.existsSync(DEBUG_STATE_FILE)) {
749
+ cachedDebugState = null;
750
+ cachedDebugAt = now;
751
+ return null;
752
+ }
753
+ const stat = fs.statSync(DEBUG_STATE_FILE);
754
+ if (stat.mtimeMs === cachedDebugMtime && cachedDebugState !== undefined) {
755
+ cachedDebugAt = now;
756
+ return cachedDebugState;
757
+ }
741
758
  const raw = fs.readFileSync(DEBUG_STATE_FILE, "utf8");
742
759
  const d = JSON.parse(raw) as DebugState;
743
- if (typeof d.debug === "boolean") return d;
760
+ if (typeof d.debug === "boolean") {
761
+ cachedDebugState = d;
762
+ cachedDebugMtime = stat.mtimeMs;
763
+ cachedDebugAt = now;
764
+ return d;
765
+ }
744
766
  } catch {}
767
+ cachedDebugState = null;
768
+ cachedDebugAt = now;
745
769
  return null;
746
770
  }
747
771
  function saveDebugState(s: DebugState): void {
@@ -751,6 +775,16 @@ function saveDebugState(s: DebugState): void {
751
775
  const tmp = `${DEBUG_STATE_FILE}.${randomUUID()}.tmp`;
752
776
  fs.writeFileSync(tmp, JSON.stringify(s, null, 2), "utf8");
753
777
  fs.renameSync(tmp, DEBUG_STATE_FILE);
778
+ // update cache
779
+ try {
780
+ const stat = fs.statSync(DEBUG_STATE_FILE);
781
+ cachedDebugState = s;
782
+ cachedDebugMtime = stat.mtimeMs;
783
+ cachedDebugAt = Date.now();
784
+ } catch {
785
+ cachedDebugState = s;
786
+ cachedDebugAt = Date.now();
787
+ }
754
788
  } catch {}
755
789
  }
756
790
  function getMinLogLevel(): number {
@@ -1419,8 +1453,8 @@ function startProxy(
1419
1453
  } catch {}
1420
1454
 
1421
1455
  const upstream: Upstream = isKilo ? "kilo" : "opencode";
1456
+ const isStream = parsedBody?.stream === true;
1422
1457
  if (!checkRateLimit(clientIP, upstream)) {
1423
- log("warn", `rate limit hit for ${upstream} from ${clientIP}`, { ip: clientIP, upstream }, reqId);
1424
1458
  res.writeHead(429, { "content-type": "application/json" });
1425
1459
  res.end(JSON.stringify({ error: "rate limit exceeded" }));
1426
1460
  return;
@@ -1474,52 +1508,49 @@ function startProxy(
1474
1508
  activeHost,
1475
1509
  );
1476
1510
  try {
1477
- if (relayBody && parsedBody) {
1511
+ if (parsedBody) {
1478
1512
  const relayBodyObj = structuredClone(parsedBody);
1479
1513
  normalizeRequestBody(relayBodyObj, true, isKilo, reqId);
1480
- relayBody = Buffer.from(JSON.stringify(relayBodyObj));
1481
- }
1482
- const response = await relayFetch(fullUrl, {
1483
- method: req.method || "POST",
1484
- headers: relayHeaders,
1485
- body: relayBody,
1486
- signal: AbortSignal.timeout(300_000),
1487
- }, reqId);
1488
- const ct =
1489
- response.headers.get("content-type") || "application/json";
1490
- if (response.ok && response.body) {
1491
- const ct =
1492
- response.headers.get("content-type") || "text/event-stream";
1493
- res.writeHead(response.status, {
1494
- "content-type": ct,
1495
- "cache-control": "no-cache, no-transform",
1496
- "connection": "keep-alive",
1497
- "x-accel-buffering": "no",
1498
- });
1499
- pipeUpstreamStream(
1500
- Readable.fromWeb(
1501
- response.body as unknown as WebReadableStream,
1502
- ),
1503
- res,
1504
- req,
1505
- reqId,
1506
- );
1507
- } else {
1508
- const data = await response.text();
1509
- const ct =
1510
- response.headers.get("content-type") || "application/json";
1511
- res.writeHead(response.status, { "content-type": ct });
1512
- res.end(data);
1514
+ const relayBody = Buffer.from(JSON.stringify(relayBodyObj));
1515
+ const response = await relayFetch(fullUrl, {
1516
+ method: req.method || "POST",
1517
+ headers: relayHeaders,
1518
+ body: relayBody,
1519
+ signal: AbortSignal.timeout(300_000),
1520
+ }, reqId);
1521
+ if (isStream && response.ok && response.body) {
1522
+ const ct =
1523
+ response.headers.get("content-type") || "text/event-stream";
1524
+ res.writeHead(response.status, {
1525
+ "content-type": ct,
1526
+ "cache-control": "no-cache, no-transform",
1527
+ "connection": "keep-alive",
1528
+ "x-accel-buffering": "no",
1529
+ });
1530
+ pipeUpstreamStream(
1531
+ Readable.fromWeb(
1532
+ response.body as unknown as WebReadableStream,
1533
+ ),
1534
+ res,
1535
+ req,
1536
+ reqId,
1537
+ );
1538
+ } else {
1539
+ const data = await response.text();
1540
+ const ct =
1541
+ response.headers.get("content-type") || "application/json";
1542
+ res.writeHead(response.status, { "content-type": ct });
1543
+ res.end(data);
1544
+ }
1545
+ return; // relay handled the response
1513
1546
  }
1514
- return; // relay handled the response
1515
1547
  } catch (e) {
1516
1548
  log("warn", "opencode relay failed, falling back to direct", {
1517
1549
  error: String(e),
1518
1550
  }, reqId);
1519
- if (res.headersSent) return; // can't recover mid-stream
1520
- }
1551
+ if (res.headersSent) return; // can't recover mid-stream
1552
+ }
1521
1553
  }
1522
- // direct path (existing, untouched)
1523
1554
  // direct path — with debug trace and thinking-aware normalize
1524
1555
  let directBody = Buffer.concat(bodyChunks);
1525
1556
  if (parsedBody) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
3
  "type": "module",
4
- "version": "1.1.8",
4
+ "version": "1.2.0",
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",