SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
7.4 KB · 170 lines typescript
Raw Blame History
1// Probe 00: GET /models (full dump), /key, /credits, and a summary of the listing.2import { raw, save, short } from "./lib.ts";34// Default call (no params) — to see whether the response is paginated / how many models come back5const first = await raw("/models");6const fb: any = first.body;7console.log("GET /models (no params)", first.status, `data=${fb?.data?.length} total_count=${fb?.total_count} links=${JSON.stringify(fb?.links)} keys=${Object.keys(fb ?? {})}`);8save("models-default-page.meta.json", { status: first.status, headers: first.headers, count: fb?.data?.length, total_count: fb?.total_count, links: fb?.links, keys: Object.keys(fb ?? {}) });910// Full dump: follow pagination with limit=100011const all: any[] = [];12let next: string | null = "/models?limit=1000";13let pages = 0;14let lastPage: any = null;15while (next) {16  const r = await raw(next.replace(/^https:\/\/openrouter\.ai\/api\/v1/, ""));17  const b: any = r.body;18  pages++;19  lastPage = b;20  all.push(...(b?.data ?? []));21  console.log(`page ${pages}: status=${r.status} data=${b?.data?.length} total_count=${b?.total_count} next=${b?.links?.next}`);22  next = b?.links?.next ?? null;23  if (pages > 10) break;24}25const models = { status: first.status, body: { ...lastPage, data: all } };26save("models.json", models.body);2728const key = await raw("/key");29console.log("GET /key", key.status, short(key.body, 1200));30save("key.json", { status: key.status, headers: key.headers, body: key.body });3132const credits = await raw("/credits");33console.log("GET /credits", credits.status, short(credits.body, 600));34save("credits.json", { status: credits.status, headers: credits.headers, body: credits.body });3536// Extra listing endpoints worth knowing about37for (const p of ["/models/user", "/models?supported_parameters=tools", "/models/count", "/auth/key"]) {38  const r = await raw(p);39  const b: any = r.body;40  console.log("GET", p, r.status, Array.isArray(b?.data) ? `data.length=${b.data.length}` : short(b, 200));41}4243const data: any[] = (models.body as any)?.data ?? [];44const count = data.length;45const outMod: Record<string, number> = {};46const inMod: Record<string, number> = {};47const params = new Map<string, number>();48const topKeys = new Set<string>();49const pricingKeys = new Set<string>();50const archKeys = new Set<string>();51const tpKeys = new Set<string>();52let free = 0;53let freeSuffix = 0;54let withExpiration = 0;55let withReasoning = 0;56let withDefaults = 0;57const reasoningSamples: any[] = [];58const defaultSamples: any[] = [];59const expSamples: any[] = [];60const prlSamples: any[] = [];61const variants: Record<string, number> = {};62const nullCtx: string[] = [];63const nullMaxOut = { count: 0 };64const tokenizers: Record<string, number> = {};65const instructTypes: Record<string, number> = {};66const modalityCombos: Record<string, number> = {};67const nonTextOut: string[] = [];68let perRequestLimitsNonNull = 0;69let hfIds = 0;7071for (const m of data) {72  for (const k of Object.keys(m)) topKeys.add(k);73  for (const k of Object.keys(m.pricing ?? {})) pricingKeys.add(k);74  for (const k of Object.keys(m.architecture ?? {})) archKeys.add(k);75  for (const k of Object.keys(m.top_provider ?? {})) tpKeys.add(k);76  const om = (m.architecture?.output_modalities ?? []).slice().sort().join("+") || "(none)";77  outMod[om] = (outMod[om] ?? 0) + 1;78  const im = (m.architecture?.input_modalities ?? []).slice().sort().join("+") || "(none)";79  inMod[im] = (inMod[im] ?? 0) + 1;80  const mod = m.architecture?.modality ?? "(none)";81  modalityCombos[mod] = (modalityCombos[mod] ?? 0) + 1;82  if (m.architecture?.tokenizer) tokenizers[m.architecture.tokenizer] = (tokenizers[m.architecture.tokenizer] ?? 0) + 1;83  const it = m.architecture?.instruct_type ?? "(null)";84  instructTypes[it] = (instructTypes[it] ?? 0) + 1;85  for (const p of m.supported_parameters ?? []) params.set(p, (params.get(p) ?? 0) + 1);86  if (m.pricing?.prompt === "0") free++;87  if (/:free$/.test(m.id)) freeSuffix++;88  const v = m.id.includes(":") ? m.id.slice(m.id.indexOf(":")) : "(none)";89  variants[v] = (variants[v] ?? 0) + 1;90  if (m.expiration_date) {91    withExpiration++;92    if (expSamples.length < 8) expSamples.push({ id: m.id, expiration_date: m.expiration_date });93  }94  if (m.reasoning != null) {95    withReasoning++;96    if (reasoningSamples.length < 12) reasoningSamples.push({ id: m.id, reasoning: m.reasoning });97  }98  if (m.default_parameters != null && Object.keys(m.default_parameters).length) {99    withDefaults++;100    if (defaultSamples.length < 8) defaultSamples.push({ id: m.id, default_parameters: m.default_parameters });101  }102  if (m.per_request_limits != null) {103    perRequestLimitsNonNull++;104    if (prlSamples.length < 3) prlSamples.push({ id: m.id, per_request_limits: m.per_request_limits });105  }106  if (m.hugging_face_id) hfIds++;107  if (m.context_length == null) nullCtx.push(m.id);108  if (m.top_provider?.max_completion_tokens == null) nullMaxOut.count++;109  const outs: string[] = m.architecture?.output_modalities ?? [];110  if (!outs.includes("text")) nonTextOut.push(`${m.id} → ${outs.join(",")}`);111}112113const reasoningShapes: Record<string, number> = {};114for (const m of data) {115  const r = m.reasoning;116  const shape = r == null ? "null/absent" : JSON.stringify(Object.fromEntries(Object.entries(r).map(([k, v]) => [k, Array.isArray(v) ? "array" : typeof v])));117  reasoningShapes[shape] = (reasoningShapes[shape] ?? 0) + 1;118}119120const summary = {121  count,122  topLevelKeys: [...topKeys],123  architectureKeys: [...archKeys],124  pricingKeys: [...pricingKeys],125  topProviderKeys: [...tpKeys],126  outputModalities: outMod,127  inputModalities: inMod,128  modalityStrings: modalityCombos,129  tokenizers,130  instructTypes,131  supportedParameters: Object.fromEntries([...params.entries()].sort((a, b) => b[1] - a[1])),132  freeByPromptZero: free,133  freeBySuffix: freeSuffix,134  variantSuffixes: variants,135  withExpirationDate: withExpiration,136  expirationSamples: expSamples,137  withReasoningField: withReasoning,138  reasoningFieldShapes: reasoningShapes,139  reasoningSamples,140  withDefaultParameters: withDefaults,141  defaultParameterSamples: defaultSamples,142  perRequestLimitsNonNull,143  perRequestLimitsSamples: prlSamples,144  huggingFaceIds: hfIds,145  nullContextLength: nullCtx,146  nullMaxCompletionTokens: nullMaxOut.count,147  nonTextOutput: nonTextOut,148  sample: data.find((m) => m.id === "anthropic/claude-haiku-4-5") ?? data[0],149};150save("models-summary.json", summary);151console.log(JSON.stringify({ ...summary, sample: undefined, nonTextOutput: summary.nonTextOutput.length, nullContextLength: nullCtx.length }, null, 1).slice(0, 6000));152153// Candidate cheap models for the probes154const want = [155  "openai/gpt-5.4-nano",156  "anthropic/claude-haiku-4-5",157  "google/gemini-3.5-flash-lite",158  "deepseek/deepseek-v4-flash",159  "x-ai/grok-4.20-0309-non-reasoning",160];161for (const w of want) {162  const m = data.find((x) => x.id === w);163  console.log(w, m ? `OK ctx=${m.context_length} in=${m.pricing.prompt} out=${m.pricing.completion} params=${(m.supported_parameters ?? []).join(",")}` : "MISSING");164}165const cheap = data166  .filter((m) => m.pricing?.prompt !== "0" && Number(m.pricing?.prompt) < 0.0000005 && (m.architecture?.output_modalities ?? []).includes("text"))167  .filter((m) => /^(openai|anthropic|google|deepseek|x-ai|mistralai|meta-llama|qwen|moonshotai|z-ai)\//.test(m.id))168  .map((m) => `${m.id} in=${m.pricing.prompt} out=${m.pricing.completion} ctx=${m.context_length} img=${(m.architecture.input_modalities ?? []).includes("image")}`);169console.log("cheap candidates:\n" + cheap.join("\n"));170