TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1// Shared helpers for the Gemini probes. No secrets here: the key comes from env GOOGLE_GEMINI_API_KEY.2import { GoogleGenAI } from "@google/genai";3import { mkdirSync, writeFileSync } from "node:fs";45export const KEY = process.env.GOOGLE_GEMINI_API_KEY ?? "";6if (!KEY) throw new Error("GOOGLE_GEMINI_API_KEY missing (set -a; . ../../.env; set +a)");78export const ai = new GoogleGenAI({ apiKey: KEY, httpOptions: { timeout: 120_000 } });910// Requested set was 3.8-flash, 3.5-flash, 3.1-pro-preview, 2.5-flash, 2.5-pro, omni — but with this free-tier key11// 2.5-* return 404 "no longer available to new users" and 3.1-pro / omni have free-tier quota 0 (see out/06-availability.json).12// Default probe set = accessible models; override with argv[2] = comma-separated ids.13export const DEFAULT_MODELS = [14 "gemini-3.8-flash",15 "gemini-3.5-flash",16 "gemini-3.5-flash-lite",17 "gemini-3.1-flash-lite",18 "gemini-3-flash-preview",19 "gemma-4-31b-it",20];21export const MODELS = process.argv[2] ? process.argv[2].split(",") : DEFAULT_MODELS;22export const SUFFIX = process.argv[2] ? "-" + process.argv[2].replace(/[^a-z0-9.-]/gi, "_") : "";2324mkdirSync("out", { recursive: true });2526export function save(name: string, data: unknown) {27 writeFileSync(`out/${name}`, JSON.stringify(data, redact, 2));28}2930// Never let a key leak into saved output.31function redact(_k: string, v: unknown) {32 if (typeof v === "string" && KEY && v.includes(KEY)) return v.replaceAll(KEY, "[REDACTED]");33 return v;34}3536export function errInfo(e: any) {37 // SDK throws ApiError {name, message, status}. message is usually the JSON error body as string.38 let parsed: any = null;39 const m: string = e?.message ?? String(e);40 try { parsed = JSON.parse(m); } catch { /* not json */ }41 const inner = parsed?.error ?? parsed;42 return {43 name: e?.name,44 httpStatus: e?.status ?? inner?.code ?? null,45 status: inner?.status ?? null,46 message: (inner?.message ?? m).slice(0, 400),47 details: inner?.details ?? null,48 };49}5051export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));5253/** Classify a 429: per-minute quota → retryable; per-day / limit 0 → give up for this model. */54export function quotaKind(e: any): "minute" | "day" | "other" {55 const info = errInfo(e);56 if (info.httpStatus !== 429) return "other";57 const q = (info.details ?? []).find((d: any) => String(d["@type"]).includes("QuotaFailure"));58 const ids: string[] = (q?.violations ?? []).map((v: any) => v.quotaId);59 if (ids.some((i) => /PerDay/.test(i))) return "day";60 if (ids.some((i) => /PerMinute/.test(i))) return "minute";61 return "other";62}6364/** Retry on per-minute 429 (honouring RetryInfo.retryDelay) and on 503 high demand; max `tries`. */65export async function withRetry<T>(fn: () => Promise<T>, tries = 4): Promise<T> {66 let last: any;67 for (let i = 0; i < tries; i++) {68 try { return await fn(); } catch (e: any) {69 last = e;70 const info = errInfo(e);71 const kind = quotaKind(e);72 if (kind === "minute" || info.httpStatus === 503) {73 const ri = (info.details ?? []).find((d: any) => String(d["@type"]).includes("RetryInfo"));74 const secs = ri?.retryDelay ? parseInt(String(ri.retryDelay)) + 2 : 20;75 console.log(` ..${info.httpStatus} (${kind}); waiting ${secs}s then retry ${i + 1}/${tries - 1}`);76 await sleep(secs * 1000);77 continue;78 }79 throw e;80 }81 }82 throw last;83}8485/** Pace requests to stay under the free-tier RPM for a given model. */86export const PACE_MS = Number(process.env.PROBE_PACE_MS ?? 7000);8788export const shortText = (r: any) =>89 (r?.candidates?.[0]?.content?.parts ?? []).filter((p: any) => p.text && !p.thought).map((p: any) => p.text).join("").slice(0, 120);90