@earendil-works/pi-coding-agent
Version:
Coding agent CLI with read, bash, edit, write tools and session management
141 lines • 5.8 kB
JavaScript
import { readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
const DEFAULT_HUGGING_FACE_URL = "https://huggingface.co";
const QUANTIZATION_PATTERN = /(?:^|[-_.])((?:UD-)?(?:IQ\d(?:_[A-Z0-9]+)+|Q\d(?:_[A-Z0-9]+)+|BF16|F16|F32|MXFP\d(?:_[A-Z0-9]+)*))$/iu;
const SHARD_SUFFIX_PATTERN = /-\d{5}-of-\d{5}$/u;
function payloadError(payload, fallback) {
if (typeof payload !== "object" || payload === null)
return fallback;
const error = payload.error;
return typeof error === "string" && error ? error : fallback;
}
function parseRateLimitDelay(value) {
const match = value?.match(/(?:^|;)t=(\d+)/u);
return match ? Number(match[1]) : undefined;
}
async function readToken(path) {
try {
const token = (await readFile(path, "utf8")).trim();
return token || undefined;
}
catch {
return undefined;
}
}
export async function findHuggingFaceToken(env = process.env) {
const fromEnvironment = env.HF_TOKEN?.trim();
if (fromEnvironment)
return fromEnvironment;
const paths = [
env.HF_TOKEN_PATH,
env.HF_HOME ? join(env.HF_HOME, "token") : undefined,
env.XDG_CACHE_HOME ? join(env.XDG_CACHE_HOME, "huggingface", "token") : undefined,
join(homedir(), ".cache", "huggingface", "token"),
].filter((path) => Boolean(path));
for (const path of new Set(paths)) {
const token = await readToken(path);
if (token)
return token;
}
return undefined;
}
export class HuggingFaceClient {
token;
baseUrl;
constructor(token, baseUrl = DEFAULT_HUGGING_FACE_URL) {
this.token = token;
this.baseUrl = baseUrl.replace(/\/+$/u, "");
}
async request(path, signal) {
const headers = new Headers();
if (this.token)
headers.set("Authorization", `Bearer ${this.token}`);
const timeout = AbortSignal.timeout(15_000);
const response = await fetch(`${this.baseUrl}${path}`, {
headers,
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
});
let payload;
try {
payload = await response.json();
}
catch {
payload = undefined;
}
if (!response.ok) {
const fallback = `Hugging Face returned HTTP ${response.status}`;
if (response.status === 429) {
const delay = Number(response.headers.get("retry-after")) || parseRateLimitDelay(response.headers.get("ratelimit"));
throw new Error(delay ? `Hugging Face rate limit reached; retry in ${delay}s` : "Hugging Face rate limit reached");
}
throw new Error(payloadError(payload, fallback));
}
return payload;
}
async search(query, signal) {
const params = new URLSearchParams({
search: query,
filter: "gguf",
sort: "downloads",
direction: "-1",
limit: "20",
});
const payload = await this.request(`/api/models?${params}`, signal);
if (!Array.isArray(payload))
throw new Error("Hugging Face returned invalid search results");
return payload.flatMap((value) => {
if (typeof value !== "object" || value === null || typeof value.id !== "string")
return [];
const model = value;
return [{ id: model.id, downloads: typeof model.downloads === "number" ? model.downloads : 0 }];
});
}
async details(id, signal) {
const encodedId = id.split("/").map(encodeURIComponent).join("/");
const payload = await this.request(`/api/models/${encodedId}?blobs=true`, signal);
if (typeof payload !== "object" || payload === null) {
throw new Error("Hugging Face returned invalid model details");
}
const model = payload;
const sizes = new Map();
if (Array.isArray(model.siblings)) {
for (const value of model.siblings) {
if (typeof value !== "object" || value === null)
continue;
const file = value;
if (typeof file.rfilename !== "string" || !file.rfilename.toLowerCase().endsWith(".gguf"))
continue;
const filename = file.rfilename.split("/").at(-1);
if (filename.toLowerCase().startsWith("mmproj"))
continue;
const stem = filename.slice(0, -5).replace(SHARD_SUFFIX_PATTERN, "");
const quantization = stem.match(QUANTIZATION_PATTERN)?.[1]?.toUpperCase();
if (!quantization)
continue;
const current = sizes.get(quantization) ?? { total: 0, complete: true };
if (typeof file.size === "number")
current.total += file.size;
else
current.complete = false;
sizes.set(quantization, current);
}
}
const quantizations = [...sizes]
.map(([name, size]) => ({ name, size: size.complete ? size.total : undefined }))
.sort((left, right) => {
if (left.name === "Q4_K_M")
return -1;
if (right.name === "Q4_K_M")
return 1;
return ((left.size ?? Number.MAX_SAFE_INTEGER) - (right.size ?? Number.MAX_SAFE_INTEGER) ||
left.name.localeCompare(right.name));
});
return {
id: typeof model.id === "string" ? model.id : id,
gated: model.gated === "auto" || model.gated === "manual" ? model.gated : false,
quantizations,
};
}
}
//# sourceMappingURL=huggingface.js.map