pi-freeflow 1.0.3 → 1.0.5

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 +77 -14
  2. package/package.json +1 -1
@@ -739,22 +739,72 @@ function checkRateLimit(ip: string, upstream: Upstream): boolean {
739
739
  // Each upstream publishes a model list; fetch it ONCE (cached) and check
740
740
  // membership. A 1-token chat probe per model was too slow (large models need
741
741
  // 10s+ for the first token). Real usability is validated at chat time (300s).
742
+ const CATALOG_CACHE_FILE = path.join(
743
+ homedir(),
744
+ ".pi",
745
+ "agent",
746
+ "pi-freeflow-catalog-cache.json",
747
+ );
748
+ const CATALOG_CACHE_TTL_MS = 3600_000; // 1 hour
749
+
750
+ interface CatalogCacheData {
751
+ timestamp: number;
752
+ opencode: string[];
753
+ kilo: string[];
754
+ }
755
+
756
+ function readCatalogCache(): CatalogCacheData | null {
757
+ try {
758
+ if (!fs.existsSync(CATALOG_CACHE_FILE)) return null;
759
+ const raw = fs.readFileSync(CATALOG_CACHE_FILE, "utf8");
760
+ const data = JSON.parse(raw) as CatalogCacheData;
761
+ if (Date.now() - data.timestamp < CATALOG_CACHE_TTL_MS) {
762
+ return data;
763
+ }
764
+ } catch {}
765
+ return null;
766
+ }
767
+
768
+ function writeCatalogCache(opencode: string[], kilo: string[]): void {
769
+ try {
770
+ const dir = path.dirname(CATALOG_CACHE_FILE);
771
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
772
+ const data: CatalogCacheData = {
773
+ timestamp: Date.now(),
774
+ opencode,
775
+ kilo,
776
+ };
777
+ const tmpPath = `${CATALOG_CACHE_FILE}.${randomUUID()}.tmp`;
778
+ fs.writeFileSync(tmpPath, JSON.stringify(data), "utf8");
779
+ fs.renameSync(tmpPath, CATALOG_CACHE_FILE);
780
+ } catch {}
781
+ }
782
+
742
783
  let opencodeCatalogP: Promise<Set<string> | null> | null = null;
743
784
  function opencodeCatalog(): Promise<Set<string> | null> {
744
785
  if (!opencodeCatalogP)
745
786
  opencodeCatalogP = (async () => {
787
+ const disk = readCatalogCache();
788
+ if (disk && Array.isArray(disk.opencode) && disk.opencode.length > 0) {
789
+ return new Set<string>(disk.opencode);
790
+ }
746
791
  try {
747
792
  const r = await fetch(`${API}/models`, {
748
793
  headers: opencodeHeaders(),
749
794
  signal: AbortSignal.timeout(10_000),
750
795
  });
751
- if (!r.ok) return null;
796
+ if (!r.ok) {
797
+ // Fallback to static known models if network fails
798
+ return new Set<string>(KNOWN_MODELS.map((m) => m.id));
799
+ }
752
800
  const d = await r.json();
753
- return new Set<string>(
754
- (d?.data ?? []).map((m: { id: string }) => m.id),
755
- );
801
+ const arr = (d?.data ?? []).map((m: { id: string }) => m.id);
802
+ const s = new Set<string>(arr);
803
+ const kDisk = disk?.kilo ?? KILO_MODELS.map((m) => m.id);
804
+ writeCatalogCache(arr, kDisk);
805
+ return s;
756
806
  } catch {
757
- return null;
807
+ return new Set<string>(KNOWN_MODELS.map((m) => m.id));
758
808
  }
759
809
  })();
760
810
  return opencodeCatalogP;
@@ -763,6 +813,10 @@ let kiloCatalogP: Promise<Set<string> | null> | null = null;
763
813
  function kiloCatalog(): Promise<Set<string> | null> {
764
814
  if (!kiloCatalogP)
765
815
  kiloCatalogP = (async () => {
816
+ const disk = readCatalogCache();
817
+ if (disk && Array.isArray(disk.kilo) && disk.kilo.length > 0) {
818
+ return new Set<string>(disk.kilo);
819
+ }
766
820
  try {
767
821
  const r = await fetch(
768
822
  KILO_CHAT_URL.replace("/chat/completions", "/models"),
@@ -771,13 +825,17 @@ function kiloCatalog(): Promise<Set<string> | null> {
771
825
  signal: AbortSignal.timeout(10_000),
772
826
  },
773
827
  );
774
- if (!r.ok) return null;
828
+ if (!r.ok) {
829
+ return new Set<string>(KILO_MODELS.map((m) => m.id));
830
+ }
775
831
  const d = await r.json();
776
- return new Set<string>(
777
- (d?.data ?? []).map((m: { id: string }) => m.id),
778
- );
832
+ const arr = (d?.data ?? []).map((m: { id: string }) => m.id);
833
+ const s = new Set<string>(arr);
834
+ const ocDisk = disk?.opencode ?? KNOWN_MODELS.map((m) => m.id);
835
+ writeCatalogCache(ocDisk, arr);
836
+ return s;
779
837
  } catch {
780
- return null;
838
+ return new Set<string>(KILO_MODELS.map((m) => m.id));
781
839
  }
782
840
  })();
783
841
  return kiloCatalogP;
@@ -863,6 +921,8 @@ function normalizeRequestBody(
863
921
  body.reasoning_effort = "max";
864
922
  } else if (re === "high" || re === "medium") {
865
923
  body.reasoning_effort = "high";
924
+ } else if (re === "minimal") {
925
+ body.reasoning_effort = "minimal";
866
926
  } else if (re === "none" || re === "off") {
867
927
  delete body.reasoning_effort;
868
928
  } else {
@@ -877,6 +937,7 @@ function normalizeRequestBody(
877
937
  const re = r.effort.toLowerCase();
878
938
  if (re === "xhigh" || re === "max") r.effort = "max";
879
939
  else if (re === "high" || re === "medium") r.effort = "high";
940
+ else if (re === "minimal") r.effort = "minimal";
880
941
  else r.effort = "low";
881
942
  }
882
943
  }
@@ -1069,8 +1130,9 @@ function startProxy(
1069
1130
  ? Buffer.concat(bodyChunks)
1070
1131
  : undefined;
1071
1132
  if (relayBody && parsedBody) {
1072
- normalizeRequestBody(parsedBody, true);
1073
- relayBody = Buffer.from(JSON.stringify(parsedBody));
1133
+ const relayBodyObj = structuredClone(parsedBody);
1134
+ normalizeRequestBody(relayBodyObj, true);
1135
+ relayBody = Buffer.from(JSON.stringify(relayBodyObj));
1074
1136
  }
1075
1137
  const response = await relayFetch(fullUrl, {
1076
1138
  method: req.method || "POST",
@@ -1114,8 +1176,9 @@ function startProxy(
1114
1176
  // direct path (existing, untouched)
1115
1177
  let directBody = Buffer.concat(bodyChunks);
1116
1178
  if (parsedBody) {
1117
- normalizeRequestBody(parsedBody, false);
1118
- directBody = Buffer.from(JSON.stringify(parsedBody));
1179
+ const directBodyObj = structuredClone(parsedBody);
1180
+ normalizeRequestBody(directBodyObj, false);
1181
+ directBody = Buffer.from(JSON.stringify(directBodyObj));
1119
1182
  }
1120
1183
  const fwd = sanitizeHeaders(req.headers, target.hostname);
1121
1184
  if (directBody.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-freeflow",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",