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.
- package/extensions/index.ts +77 -14
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
754
|
-
|
|
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
|
|
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)
|
|
828
|
+
if (!r.ok) {
|
|
829
|
+
return new Set<string>(KILO_MODELS.map((m) => m.id));
|
|
830
|
+
}
|
|
775
831
|
const d = await r.json();
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
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
|
-
|
|
1073
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
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) {
|