Compare commits

...

1 Commits

Author SHA1 Message Date
Sayan Sisodiya
78b8209397 add script to customize model slugs in models.json 2026-02-23 12:35:50 -08:00

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""Fetch a Codex models.json from a public GitHub tag and prefix model slugs.
Example:
python scripts/generate_prefixed_model_catalog.py \
--tag 0.104.0 \
--prefix custom/ \
--out ./models.custom.json
This custom models.json can be used to override the default models.json used
by Codex via the `model_catalog_json` config option.
"""
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.request
from pathlib import Path
REPO = "openai/codex"
RAW_MODELS_URL = "https://raw.githubusercontent.com/{repo}/{tag}/codex-rs/core/models.json"
HTTP_TIMEOUT_SECONDS = 15.0
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Fetch codex-rs/core/models.json for a public openai/codex tag and prefix model slugs."
)
)
parser.add_argument("--tag", required=True, help="Git tag/branch/commit to fetch")
parser.add_argument(
"--prefix",
required=True,
help="Prefix to add to each model slug (e.g. 'custom/' or 'gateway/')",
)
parser.add_argument("--out", required=True, help="Output path for the generated JSON")
return parser.parse_args()
def fetch_catalog(repo: str, tag: str, timeout_seconds: float) -> tuple[str, dict]:
url = RAW_MODELS_URL.format(repo=repo, tag=tag)
request = urllib.request.Request(
url,
headers={"User-Agent": "codex-model-catalog-prefixer/1"},
)
with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
payload = response.read()
return url, json.loads(payload.decode("utf-8"))
def normalize_tag(tag: str) -> str:
if tag.startswith("rust-"):
return tag
if tag.startswith("v"):
return f"rust-{tag}"
return f"rust-v{tag}"
def prefixed(prefix: str, value: str) -> str:
if value.startswith(prefix):
return value
return f"{prefix}{value}"
def rewrite_catalog(catalog: dict, prefix: str) -> dict[str, int]:
models = catalog.get("models")
if not isinstance(models, list):
raise ValueError("expected JSON object with a top-level 'models' list")
slug_updates = 0
upgrade_updates = 0
for idx, model in enumerate(models):
if not isinstance(model, dict):
raise ValueError(f"models[{idx}] is not an object")
slug = model.get("slug")
if not isinstance(slug, str):
raise ValueError(f"models[{idx}].slug is missing or not a string")
new_slug = prefixed(prefix, slug)
if new_slug != slug:
model["slug"] = new_slug
slug_updates += 1
upgrade = model.get("upgrade")
if isinstance(upgrade, dict):
upgrade_model = upgrade.get("model")
if isinstance(upgrade_model, str):
new_upgrade_model = prefixed(prefix, upgrade_model)
if new_upgrade_model != upgrade_model:
upgrade["model"] = new_upgrade_model
upgrade_updates += 1
return {
"models": len(models),
"slug_updates": slug_updates,
"upgrade_model_updates": upgrade_updates,
}
def main() -> int:
args = parse_args()
if not args.prefix:
print("error: --prefix must not be empty", file=sys.stderr)
return 2
tag = normalize_tag(args.tag)
try:
source_url, catalog = fetch_catalog(REPO, tag, HTTP_TIMEOUT_SECONDS)
stats = rewrite_catalog(catalog, args.prefix)
except urllib.error.HTTPError as err:
print(f"error: failed to fetch models.json ({err.code}): {err.url}", file=sys.stderr)
return 1
except urllib.error.URLError as err:
print(f"error: network error while fetching models.json: {err}", file=sys.stderr)
return 1
except json.JSONDecodeError as err:
print(f"error: fetched file is not valid JSON: {err}", file=sys.stderr)
return 1
except ValueError as err:
print(f"error: unexpected models.json shape: {err}", file=sys.stderr)
return 1
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8")
print(f"Fetched: {source_url}")
print(f"Wrote: {out_path}")
print(
"Models: {models} (updated {slug_updates}); upgrade.model updated: "
"{upgrade_model_updates}".format(**stats)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())