pi-freeflow 1.0.3 → 1.0.4
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 +72 -14
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -739,22 +739,70 @@ 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
|
+
fs.writeFileSync(CATALOG_CACHE_FILE, JSON.stringify(data), "utf8");
|
|
778
|
+
} catch {}
|
|
779
|
+
}
|
|
780
|
+
|
|
742
781
|
let opencodeCatalogP: Promise<Set<string> | null> | null = null;
|
|
743
782
|
function opencodeCatalog(): Promise<Set<string> | null> {
|
|
744
783
|
if (!opencodeCatalogP)
|
|
745
784
|
opencodeCatalogP = (async () => {
|
|
785
|
+
const disk = readCatalogCache();
|
|
786
|
+
if (disk && Array.isArray(disk.opencode) && disk.opencode.length > 0) {
|
|
787
|
+
return new Set<string>(disk.opencode);
|
|
788
|
+
}
|
|
746
789
|
try {
|
|
747
790
|
const r = await fetch(`${API}/models`, {
|
|
748
791
|
headers: opencodeHeaders(),
|
|
749
792
|
signal: AbortSignal.timeout(10_000),
|
|
750
793
|
});
|
|
751
|
-
if (!r.ok)
|
|
794
|
+
if (!r.ok) {
|
|
795
|
+
// Fallback to static known models if network fails
|
|
796
|
+
return new Set<string>(KNOWN_MODELS.map((m) => m.id));
|
|
797
|
+
}
|
|
752
798
|
const d = await r.json();
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
);
|
|
799
|
+
const arr = (d?.data ?? []).map((m: { id: string }) => m.id);
|
|
800
|
+
const s = new Set<string>(arr);
|
|
801
|
+
const kDisk = disk?.kilo ?? KILO_MODELS.map((m) => m.id);
|
|
802
|
+
writeCatalogCache(arr, kDisk);
|
|
803
|
+
return s;
|
|
756
804
|
} catch {
|
|
757
|
-
return
|
|
805
|
+
return new Set<string>(KNOWN_MODELS.map((m) => m.id));
|
|
758
806
|
}
|
|
759
807
|
})();
|
|
760
808
|
return opencodeCatalogP;
|
|
@@ -763,6 +811,10 @@ let kiloCatalogP: Promise<Set<string> | null> | null = null;
|
|
|
763
811
|
function kiloCatalog(): Promise<Set<string> | null> {
|
|
764
812
|
if (!kiloCatalogP)
|
|
765
813
|
kiloCatalogP = (async () => {
|
|
814
|
+
const disk = readCatalogCache();
|
|
815
|
+
if (disk && Array.isArray(disk.kilo) && disk.kilo.length > 0) {
|
|
816
|
+
return new Set<string>(disk.kilo);
|
|
817
|
+
}
|
|
766
818
|
try {
|
|
767
819
|
const r = await fetch(
|
|
768
820
|
KILO_CHAT_URL.replace("/chat/completions", "/models"),
|
|
@@ -771,13 +823,17 @@ function kiloCatalog(): Promise<Set<string> | null> {
|
|
|
771
823
|
signal: AbortSignal.timeout(10_000),
|
|
772
824
|
},
|
|
773
825
|
);
|
|
774
|
-
if (!r.ok)
|
|
826
|
+
if (!r.ok) {
|
|
827
|
+
return new Set<string>(KILO_MODELS.map((m) => m.id));
|
|
828
|
+
}
|
|
775
829
|
const d = await r.json();
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
);
|
|
830
|
+
const arr = (d?.data ?? []).map((m: { id: string }) => m.id);
|
|
831
|
+
const s = new Set<string>(arr);
|
|
832
|
+
const ocDisk = disk?.opencode ?? KNOWN_MODELS.map((m) => m.id);
|
|
833
|
+
writeCatalogCache(ocDisk, arr);
|
|
834
|
+
return s;
|
|
779
835
|
} catch {
|
|
780
|
-
return
|
|
836
|
+
return new Set<string>(KILO_MODELS.map((m) => m.id));
|
|
781
837
|
}
|
|
782
838
|
})();
|
|
783
839
|
return kiloCatalogP;
|
|
@@ -1069,8 +1125,9 @@ function startProxy(
|
|
|
1069
1125
|
? Buffer.concat(bodyChunks)
|
|
1070
1126
|
: undefined;
|
|
1071
1127
|
if (relayBody && parsedBody) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1128
|
+
const relayBodyObj = structuredClone(parsedBody);
|
|
1129
|
+
normalizeRequestBody(relayBodyObj, true);
|
|
1130
|
+
relayBody = Buffer.from(JSON.stringify(relayBodyObj));
|
|
1074
1131
|
}
|
|
1075
1132
|
const response = await relayFetch(fullUrl, {
|
|
1076
1133
|
method: req.method || "POST",
|
|
@@ -1114,8 +1171,9 @@ function startProxy(
|
|
|
1114
1171
|
// direct path (existing, untouched)
|
|
1115
1172
|
let directBody = Buffer.concat(bodyChunks);
|
|
1116
1173
|
if (parsedBody) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1174
|
+
const directBodyObj = structuredClone(parsedBody);
|
|
1175
|
+
normalizeRequestBody(directBodyObj, false);
|
|
1176
|
+
directBody = Buffer.from(JSON.stringify(directBodyObj));
|
|
1119
1177
|
}
|
|
1120
1178
|
const fwd = sanitizeHeaders(req.headers, target.hostname);
|
|
1121
1179
|
if (directBody.length > 0) {
|