pi-freeflow 1.0.1 → 1.0.3

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 +59 -26
  2. package/package.json +1 -1
@@ -13,6 +13,14 @@ import { Readable } from "node:stream";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import type { ExtensionAPI, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
15
15
 
16
+
17
+ // Ensure subagents and OMP resolver recognize active credentials for freeflow
18
+ if (!process.env.FREEFLOW_API_KEY) {
19
+ process.env.FREEFLOW_API_KEY = "freeflow";
20
+ }
21
+ if (!process.env.BANSOS_API_KEY) {
22
+ process.env.BANSOS_API_KEY = "freeflow";
23
+ }
16
24
  // ── Configuration ──────────────────────────────────────────────────
17
25
  const UPSTREAM_OPENCODE = "https://opencode.ai/zen";
18
26
  // KiloCode gateway — OpenAI-compatible; free models are keyless (200 req/hr per IP)
@@ -836,6 +844,54 @@ function sanitizeHeaders(
836
844
  return sanitized;
837
845
  }
838
846
 
847
+ function normalizeRequestBody(
848
+ body: Record<string, unknown>,
849
+ isRelay = false,
850
+ ): Record<string, unknown> {
851
+ // 1. Tool choice normalization (OpenCode Zen only supports "auto" or undefined)
852
+ if (body.tool_choice === "none") {
853
+ delete body.tool_choice;
854
+ delete body.tools;
855
+ } else if (body.tool_choice && body.tool_choice !== "auto") {
856
+ body.tool_choice = "auto";
857
+ }
858
+
859
+ // 2. Reasoning normalization
860
+ if (typeof body.reasoning_effort === "string") {
861
+ const re = body.reasoning_effort.toLowerCase();
862
+ if (re === "xhigh" || re === "max") {
863
+ body.reasoning_effort = "max";
864
+ } else if (re === "high" || re === "medium") {
865
+ body.reasoning_effort = "high";
866
+ } else if (re === "none" || re === "off") {
867
+ delete body.reasoning_effort;
868
+ } else {
869
+ body.reasoning_effort = "low";
870
+ }
871
+ }
872
+ if (body.reasoning && typeof body.reasoning === "object") {
873
+ const r = body.reasoning as Record<string, unknown>;
874
+ if (r.effort === "none" || r.effort === "off") {
875
+ delete r.effort;
876
+ } else if (typeof r.effort === "string") {
877
+ const re = r.effort.toLowerCase();
878
+ if (re === "xhigh" || re === "max") r.effort = "max";
879
+ else if (re === "high" || re === "medium") r.effort = "high";
880
+ else r.effort = "low";
881
+ }
882
+ }
883
+
884
+ // 3. Max tokens clamp for Vercel relay
885
+ if (isRelay) {
886
+ const mt = body.max_tokens ?? body.maxTokens;
887
+ if (typeof mt === "number" && mt > RELAY_MAX_TOKENS) {
888
+ body.max_tokens = RELAY_MAX_TOKENS;
889
+ }
890
+ }
891
+
892
+ return body;
893
+ }
894
+
839
895
  // ponytail: shared stream pipe — upstream abort/timeout must end response,
840
896
  // not become an uncaught exception that crashes pi.
841
897
  function pipeUpstreamStream(
@@ -1009,27 +1065,11 @@ function startProxy(
1009
1065
  activeHost,
1010
1066
  );
1011
1067
  try {
1012
- // ponytail: clamp max_tokens for Vercel relay — large values
1013
- // cause 400 "Upstream request failed" (response size / duration
1014
- // limits). Direct mode stays unconstrained.
1015
1068
  let relayBody = bodyChunks.length
1016
1069
  ? Buffer.concat(bodyChunks)
1017
1070
  : undefined;
1018
1071
  if (relayBody && parsedBody) {
1019
- const mt = parsedBody.max_tokens ?? parsedBody.maxTokens;
1020
- if (typeof mt === "number" && mt > RELAY_MAX_TOKENS) {
1021
- parsedBody.max_tokens = RELAY_MAX_TOKENS;
1022
- }
1023
- if (typeof parsedBody.reasoning_effort === "string") {
1024
- const re = parsedBody.reasoning_effort.toLowerCase();
1025
- if (re === "xhigh" || re === "max") {
1026
- parsedBody.reasoning_effort = "max";
1027
- } else if (re === "high" || re === "medium") {
1028
- parsedBody.reasoning_effort = "high";
1029
- } else {
1030
- parsedBody.reasoning_effort = "low";
1031
- }
1032
- }
1072
+ normalizeRequestBody(parsedBody, true);
1033
1073
  relayBody = Buffer.from(JSON.stringify(parsedBody));
1034
1074
  }
1035
1075
  const response = await relayFetch(fullUrl, {
@@ -1073,15 +1113,8 @@ function startProxy(
1073
1113
  }
1074
1114
  // direct path (existing, untouched)
1075
1115
  let directBody = Buffer.concat(bodyChunks);
1076
- if (parsedBody && typeof parsedBody.reasoning_effort === "string") {
1077
- const re = parsedBody.reasoning_effort.toLowerCase();
1078
- if (re === "xhigh" || re === "max") {
1079
- parsedBody.reasoning_effort = "max";
1080
- } else if (re === "high" || re === "medium") {
1081
- parsedBody.reasoning_effort = "high";
1082
- } else {
1083
- parsedBody.reasoning_effort = "low";
1084
- }
1116
+ if (parsedBody) {
1117
+ normalizeRequestBody(parsedBody, false);
1085
1118
  directBody = Buffer.from(JSON.stringify(parsedBody));
1086
1119
  }
1087
1120
  const fwd = sanitizeHeaders(req.headers, target.hostname);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Personal multi-cloud rolling fallback relay for OpenCode Zen and KiloCode models in OMP/Pi",
5
5
  "keywords": [
6
6
  "pi-package",