Python 88.3%
TypeScript 7.6%
Shell 4.1%
1#!/usr/bin/env python32"""Re-runnable Anthropic model discovery.34GET /v1/models (paginated, limit=100) -> sanitized sources/anthropic/models-api-raw.json5Diff against the previous file -> sources/anthropic/models-diff.json, prints NEW / REMOVED / CHANGED ids.67Usage: python3 scripts/discover_anthropic_models.py [--limit N] [--dry-run]8Auth comes from .env via scripts/live.py (never printed). Each call is logged to reports/live-requests.jsonl.9"""10from __future__ import annotations1112import argparse13import json14import sys15from pathlib import Path1617sys.path.insert(0, str(Path(__file__).resolve().parent))18import live # noqa: E402 (scripts/live.py)1920ROOT = Path(__file__).resolve().parents[1]21RAW = ROOT / "sources" / "anthropic" / "models-api-raw.json"22DIFF = ROOT / "sources" / "anthropic" / "models-diff.json"232425def fetch_all(limit: int) -> dict:26 data: list[dict] = []27 after: str | None = None28 pages = 029 while True:30 q = f"?limit={limit}" + (f"&after_id={after}" if after else "")31 st, body, _ = live.anthropic_request("GET", f"/v1/models{q}", note=f"discover models page {pages + 1}")32 if st != 200:33 raise SystemExit(f"GET /v1/models -> HTTP {st}: {json.dumps(body)[:300]}")34 data.extend(body.get("data", []))35 pages += 136 if not body.get("has_more"):37 break38 after = body.get("last_id")39 if not after:40 break41 return {"data": data, "has_more": False, "first_id": data[0]["id"] if data else None,42 "last_id": data[-1]["id"] if data else None, "_pages": pages, "_retrieved_at": live.now()}434445def index(doc: dict) -> dict[str, dict]:46 return {m["id"]: m for m in doc.get("data", [])}474849def main() -> None:50 ap = argparse.ArgumentParser()51 ap.add_argument("--limit", type=int, default=100)52 ap.add_argument("--dry-run", action="store_true", help="fetch and diff, do not write")53 args = ap.parse_args()5455 prev = json.loads(RAW.read_text()) if RAW.exists() else {"data": []}56 cur = fetch_all(args.limit)57 pi, ci = index(prev), index(cur)58 new = sorted(set(ci) - set(pi))59 removed = sorted(set(pi) - set(ci))60 changed = sorted(i for i in set(ci) & set(pi) if ci[i] != pi[i])61 diff = {"retrieved_at": cur["_retrieved_at"], "previous_count": len(pi), "current_count": len(ci),62 "new": new, "removed": removed, "changed": [{"id": i, "before": pi[i], "after": ci[i]} for i in changed],63 "pages_fetched": cur["_pages"]}64 print(f"models: {len(ci)} (previous {len(pi)}), pages={cur['_pages']}")65 for i in new:66 print(f"NEW {i} created_at={ci[i].get('created_at')}")67 for i in removed:68 print(f"REMOVED {i}")69 for i in changed:70 print(f"CHANGED {i}")71 if not (new or removed or changed):72 print("no changes")73 if args.dry_run:74 return75 out = {k: v for k, v in cur.items() if not k.startswith("_")}76 live.save_sanitized(out, RAW)77 live.save_sanitized(diff, DIFF)78 print(f"wrote {RAW.relative_to(ROOT)} and {DIFF.relative_to(ROOT)}")798081if __name__ == "__main__":82 main()83