pi-freeflow 1.0.7 → 1.0.9

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 +42 -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,21 @@ 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 clampTokens = (val: number): number => {
952
+ if (val < 16) return 16;
953
+ if (isRelay && val > RELAY_MAX_TOKENS) return RELAY_MAX_TOKENS;
954
+ return val;
955
+ };
956
+
957
+ if (typeof body.max_tokens === "number") {
958
+ body.max_tokens = clampTokens(body.max_tokens);
959
+ }
960
+ if (typeof body.maxTokens === "number") {
961
+ body.maxTokens = clampTokens(body.maxTokens);
962
+ }
963
+ if (typeof body.max_output_tokens === "number") {
964
+ body.max_output_tokens = clampTokens(body.max_output_tokens);
951
965
  }
952
966
 
953
967
  return body;
@@ -1002,6 +1016,12 @@ function pipeUpstreamStream(
1002
1016
  req.on("close", () => {
1003
1017
  if (!nodeStream.destroyed) nodeStream.destroy();
1004
1018
  });
1019
+ res.on("close", () => {
1020
+ if (!nodeStream.destroyed) nodeStream.destroy();
1021
+ });
1022
+ res.on("error", () => {
1023
+ if (!nodeStream.destroyed) nodeStream.destroy();
1024
+ });
1005
1025
  }
1006
1026
 
1007
1027
  // ── Start local proxy ──────────────────────────────────────────────
@@ -1083,13 +1103,15 @@ function startProxy(
1083
1103
  if (isKilo && parsedBody) {
1084
1104
  // KiloCode gateway routing (free models are keyless)
1085
1105
  const isStream = parsedBody.stream === true;
1106
+ const kiloBodyObj = structuredClone(parsedBody);
1107
+ normalizeRequestBody(kiloBodyObj, true);
1086
1108
  const response = await relayFetch(KILO_CHAT_URL, {
1087
1109
  method: "POST",
1088
1110
  headers: {
1089
1111
  "Content-Type": "application/json",
1090
1112
  Authorization: "Bearer kilo-free",
1091
1113
  },
1092
- body: JSON.stringify(parsedBody),
1114
+ body: JSON.stringify(kiloBodyObj),
1093
1115
  signal: AbortSignal.timeout(300_000),
1094
1116
  });
1095
1117
  if (isStream && response.ok && response.body) {
@@ -1215,12 +1237,18 @@ function startProxy(
1215
1237
  res.end();
1216
1238
  }
1217
1239
  });
1218
- proxy.setTimeout(30_000, () => {
1240
+ proxy.setTimeout(300_000, () => {
1219
1241
  proxy.destroy(new Error("timeout"));
1220
1242
  });
1221
1243
  req.on("aborted", () => {
1222
1244
  if (!proxy.destroyed) proxy.destroy();
1223
1245
  });
1246
+ req.on("close", () => {
1247
+ if (!proxy.destroyed) proxy.destroy();
1248
+ });
1249
+ res.on("close", () => {
1250
+ if (!proxy.destroyed) proxy.destroy();
1251
+ });
1224
1252
  // ponytail: body already buffered in bodyChunks above for model routing;
1225
1253
  // req is drained so pipe() would send an empty body → upstream hang → 502.
1226
1254
  proxy.end(directBody);
@@ -1273,7 +1301,8 @@ async function isProxyAlive(port: number): Promise<boolean> {
1273
1301
  const res = await fetch(`http://${HOST}:${port}/v1/models`, {
1274
1302
  signal: AbortSignal.timeout(500),
1275
1303
  });
1276
- return res.ok;
1304
+ const ct = res.headers.get("content-type") || "";
1305
+ return res.ok && ct.includes("application/json");
1277
1306
  } catch {
1278
1307
  return false;
1279
1308
  }
@@ -1611,7 +1640,7 @@ export default async function (pi: ExtensionAPI) {
1611
1640
  pi.registerCommand("bansos", commandSpec);
1612
1641
 
1613
1642
  // Reload persisted state on session start/resume (env overrides still win).
1614
- pi.on("session_start", async (_event, ctx) => {
1643
+ pi.on?.("session_start", async (_event, ctx) => {
1615
1644
  relayState = resolveRelayState();
1616
1645
  statusUi = ctx.ui;
1617
1646
  ctx.ui?.setStatus?.("freeflow", undefined);
@@ -1620,7 +1649,7 @@ export default async function (pi: ExtensionAPI) {
1620
1649
 
1621
1650
  // Pi normally pauses after threshold compaction. Queue a follow-up while the
1622
1651
  // original run is still active so the core agent continues automatically.
1623
- pi.on("session_compact", (event, ctx) => {
1652
+ pi.on?.("session_compact", (event, ctx) => {
1624
1653
  if (
1625
1654
  event.reason !== "threshold" ||
1626
1655
  event.willRetry ||
@@ -1629,7 +1658,7 @@ export default async function (pi: ExtensionAPI) {
1629
1658
  ) {
1630
1659
  return;
1631
1660
  }
1632
- pi.sendUserMessage(
1661
+ pi.sendUserMessage?.(
1633
1662
  "Continue the current task from the compacted context. Do not wait for another user message; proceed with the next required step.",
1634
1663
  { deliverAs: "followUp" },
1635
1664
  );
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.9",
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",