pi-freeflow 1.0.7 → 1.0.8

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 +37 -13
  2. package/package.json +2 -1
@@ -99,7 +99,11 @@ function loadRelayState(): RelayState {
99
99
  }
100
100
  function saveRelayState(s: RelayState): void {
101
101
  try {
102
- fs.writeFileSync(RELAY_STATE_FILE, JSON.stringify(s));
102
+ const dir = path.dirname(RELAY_STATE_FILE);
103
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
104
+ const tmpPath = `${RELAY_STATE_FILE}.${randomUUID()}.tmp`;
105
+ fs.writeFileSync(tmpPath, JSON.stringify(s, null, 2), "utf8");
106
+ fs.renameSync(tmpPath, RELAY_STATE_FILE);
103
107
  } catch (e) {
104
108
  log("warn", "could not persist relay state", { error: String(e) });
105
109
  }
@@ -663,6 +667,7 @@ const ALLOWED_METHODS = new Set(["GET", "POST", "OPTIONS", "HEAD"]);
663
667
  const STRIP_HEADERS = new Set([
664
668
  "authorization",
665
669
  "host",
670
+ "content-length",
666
671
  "x-forwarded-for",
667
672
  "x-forwarded-host",
668
673
  "x-forwarded-proto",
@@ -942,12 +947,16 @@ function normalizeRequestBody(
942
947
  }
943
948
  }
944
949
 
945
- // 3. Max tokens clamp for Vercel relay
946
- if (isRelay) {
947
- const mt = body.max_tokens ?? body.maxTokens;
948
- if (typeof mt === "number" && mt > RELAY_MAX_TOKENS) {
949
- body.max_tokens = RELAY_MAX_TOKENS;
950
- }
950
+ // 3. Max & Min tokens clamping (OpenCode Zen requires min 16; Vercel relay caps at 64k)
951
+ const mt = (body.max_tokens ?? body.maxTokens ?? body.max_output_tokens) as number | undefined;
952
+ if (typeof mt === "number") {
953
+ let clamped = mt;
954
+ if (clamped < 16) clamped = 16;
955
+ else if (isRelay && clamped > RELAY_MAX_TOKENS) clamped = RELAY_MAX_TOKENS;
956
+
957
+ if (body.max_tokens !== undefined) body.max_tokens = clamped;
958
+ if (body.maxTokens !== undefined) body.maxTokens = clamped;
959
+ if (body.max_output_tokens !== undefined) body.max_output_tokens = clamped;
951
960
  }
952
961
 
953
962
  return body;
@@ -1002,6 +1011,12 @@ function pipeUpstreamStream(
1002
1011
  req.on("close", () => {
1003
1012
  if (!nodeStream.destroyed) nodeStream.destroy();
1004
1013
  });
1014
+ res.on("close", () => {
1015
+ if (!nodeStream.destroyed) nodeStream.destroy();
1016
+ });
1017
+ res.on("error", () => {
1018
+ if (!nodeStream.destroyed) nodeStream.destroy();
1019
+ });
1005
1020
  }
1006
1021
 
1007
1022
  // ── Start local proxy ──────────────────────────────────────────────
@@ -1083,13 +1098,15 @@ function startProxy(
1083
1098
  if (isKilo && parsedBody) {
1084
1099
  // KiloCode gateway routing (free models are keyless)
1085
1100
  const isStream = parsedBody.stream === true;
1101
+ const kiloBodyObj = structuredClone(parsedBody);
1102
+ normalizeRequestBody(kiloBodyObj, true);
1086
1103
  const response = await relayFetch(KILO_CHAT_URL, {
1087
1104
  method: "POST",
1088
1105
  headers: {
1089
1106
  "Content-Type": "application/json",
1090
1107
  Authorization: "Bearer kilo-free",
1091
1108
  },
1092
- body: JSON.stringify(parsedBody),
1109
+ body: JSON.stringify(kiloBodyObj),
1093
1110
  signal: AbortSignal.timeout(300_000),
1094
1111
  });
1095
1112
  if (isStream && response.ok && response.body) {
@@ -1215,12 +1232,18 @@ function startProxy(
1215
1232
  res.end();
1216
1233
  }
1217
1234
  });
1218
- proxy.setTimeout(30_000, () => {
1235
+ proxy.setTimeout(300_000, () => {
1219
1236
  proxy.destroy(new Error("timeout"));
1220
1237
  });
1221
1238
  req.on("aborted", () => {
1222
1239
  if (!proxy.destroyed) proxy.destroy();
1223
1240
  });
1241
+ req.on("close", () => {
1242
+ if (!proxy.destroyed) proxy.destroy();
1243
+ });
1244
+ res.on("close", () => {
1245
+ if (!proxy.destroyed) proxy.destroy();
1246
+ });
1224
1247
  // ponytail: body already buffered in bodyChunks above for model routing;
1225
1248
  // req is drained so pipe() would send an empty body → upstream hang → 502.
1226
1249
  proxy.end(directBody);
@@ -1273,7 +1296,8 @@ async function isProxyAlive(port: number): Promise<boolean> {
1273
1296
  const res = await fetch(`http://${HOST}:${port}/v1/models`, {
1274
1297
  signal: AbortSignal.timeout(500),
1275
1298
  });
1276
- return res.ok;
1299
+ const ct = res.headers.get("content-type") || "";
1300
+ return res.ok && ct.includes("application/json");
1277
1301
  } catch {
1278
1302
  return false;
1279
1303
  }
@@ -1611,7 +1635,7 @@ export default async function (pi: ExtensionAPI) {
1611
1635
  pi.registerCommand("bansos", commandSpec);
1612
1636
 
1613
1637
  // Reload persisted state on session start/resume (env overrides still win).
1614
- pi.on("session_start", async (_event, ctx) => {
1638
+ pi.on?.("session_start", async (_event, ctx) => {
1615
1639
  relayState = resolveRelayState();
1616
1640
  statusUi = ctx.ui;
1617
1641
  ctx.ui?.setStatus?.("freeflow", undefined);
@@ -1620,7 +1644,7 @@ export default async function (pi: ExtensionAPI) {
1620
1644
 
1621
1645
  // Pi normally pauses after threshold compaction. Queue a follow-up while the
1622
1646
  // original run is still active so the core agent continues automatically.
1623
- pi.on("session_compact", (event, ctx) => {
1647
+ pi.on?.("session_compact", (event, ctx) => {
1624
1648
  if (
1625
1649
  event.reason !== "threshold" ||
1626
1650
  event.willRetry ||
@@ -1629,7 +1653,7 @@ export default async function (pi: ExtensionAPI) {
1629
1653
  ) {
1630
1654
  return;
1631
1655
  }
1632
- pi.sendUserMessage(
1656
+ pi.sendUserMessage?.(
1633
1657
  "Continue the current task from the compacted context. Do not wait for another user message; proceed with the next required step.",
1634
1658
  { deliverAs: "followUp" },
1635
1659
  );
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
- "version": "1.0.7",
3
+ "type": "module",
4
+ "version": "1.0.8",
4
5
  "description": "Personal multi-cloud rolling fallback relay for OpenCode Zen and KiloCode models in OMP/Pi",
5
6
  "keywords": [
6
7
  "pi-package",