← Back to Gists

devplace_recreate_models.py - Recreate Models After Rename

📝 Python
retoor
retoor · Level 54802 ·

Recreates gateway models with known-good configurations after prefix stripping. Checks existing state, avoids duplicates.

Python
#!/usr/bin/env python3
"""Recreate all devplace models that had 'opencode-' prefix stripped.

Since the original opencode-* models were deleted in previous runs, this script
recreates them with the new names (prefix removed) using known configuration.
"""

import argparse
import json
import logging
import os
import sys
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

BASE_URL = "https://devplace.net"
API_KEY_ENV_VAR = "DEVPLACE_API_KEY"
LOG_FORMAT = "%(asctime)s [%(levelname)s] %(message)s"

logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
logger = logging.getLogger(__name__)


def get_api_key() -> str:
    """Read the API key from environment variable or .env file."""
    env_value = os.environ.get(API_KEY_ENV_VAR)
    if env_value:
        logger.info("API key found in environment variable %s", API_KEY_ENV_VAR)
        return env_value

    env_file = Path(".env")
    if env_file.exists():
        for line in env_file.read_text().splitlines():
            line = line.strip()
            if line.startswith("#") or "=" not in line:
                continue
            key, _, value = line.partition("=")
            if key.strip() == API_KEY_ENV_VAR and value.strip():
                logger.info("API key found in .env file")
                return value.strip()

    logger.error("No API key found. Set %s env var or add to .env file.", API_KEY_ENV_VAR)
    sys.exit(1)


def api_request(method: str, path: str, data: dict | None = None) -> dict:
    """Send an authenticated form request to the DevPlace admin API."""
    url = f"{BASE_URL}{path}"
    headers = {
        "Accept": "application/json",
        "X-API-KEY": get_api_key(),
    }
    if data is not None:
        headers["Content-Type"] = "application/x-www-form-urlencoded"

    body = None
    if data is not None:
        body = urlencode(data).encode("utf-8")
        logger.debug("Request body: %s", body.decode())

    req = Request(url, data=body, headers=headers, method=method)
    logger.info("%s %s", method, url)

    try:
        with urlopen(req, timeout=30) as resp:
            raw = resp.read().decode("utf-8")
            status = resp.status
            logger.debug("Response status: %d", status)
            logger.debug("Response body (first 500 chars): %s", raw[:500])
            if raw:
                return json.loads(raw)
            return {}
    except HTTPError as exc:
        logger.error("HTTP error %d on %s %s: %s", exc.code, method, url, exc.read().decode("utf-8")[:500])
        raise
    except URLError as exc:
        logger.error("URL error on %s %s: %s", method, url, exc.reason)
        raise


# Known opencode-* models and their target_model values (from earlier API responses)
OPENCODE_MODELS = [
    {"source_model": "opencode-big-pickle", "target_model": "big-pickle"},
    {"source_model": "opencode-deepseek-v4-flash", "target_model": "deepseek-v4-flash"},
    {"source_model": "opencode-deepseek-v4-flash-free", "target_model": "deepseek-v4-flash-free"},
    {"source_model": "opencode-deepseek-v4-pro", "target_model": "deepseek-v4-pro"},
    {"source_model": "opencode-glm-5", "target_model": "glm-5"},
    {"source_model": "opencode-glm-5.1", "target_model": "glm-5.1"},
    {"source_model": "opencode-glm-5.2", "target_model": "glm-5.2"},
    {"source_model": "opencode-kimi-k2.5", "target_model": "kimi-k2.5"},
    {"source_model": "opencode-kimi-k2.6", "target_model": "kimi-k2.6"},
    {"source_model": "opencode-kimi-k2.7-code", "target_model": "kimi-k2.7-code"},
    {"source_model": "opencode-kimi-k3", "target_model": "kimi-k3"},
    {"source_model": "opencode-laguna-s-2.1-free", "target_model": "laguna-s-2.1-free"},
    {"source_model": "opencode-ling-3.0-flash-fin-free", "target_model": "ling-3.0-flash-fin-free"},
    {"source_model": "opencode-mimo-v2.5-free", "target_model": "mimo-v2.5-free"},
    {"source_model": "opencode-minimax-m2.5", "target_model": "minimax-m2.5"},
    {"source_model": "opencode-minimax-m2.7", "target_model": "minimax-m2.7"},
    {"source_model": "opencode-minimax-m3", "target_model": "minimax-m3"},
    {"source_model": "opencode-nemotron-3-ultra-free", "target_model": "nemotron-3-ultra-free"},
    {"source_model": "opencode-nemotron-3.5-lightning-free", "target_model": "nemotron-3.5-lightning-free"},
]


def create_model(source_name: str, target_name: str) -> bool:
    """Create a new gateway model."""
    path = "/admin/gateway/models/new"
    form_data = {
        "source_model": source_name,
        "target_model": target_name,
        "kind": "chat",
        "provider": "opencode",
        "context_window": "0",
        "price_cache_hit_per_m": "0.0",
        "price_cache_miss_per_m": "0.0",
        "price_output_per_m": "0.0",
        "price_input_per_m": "0.0",
        "context_tier_threshold_tokens": "0",
    }
    try:
        api_request("POST", path, form_data)
        logger.info("Created model '%s' -> '%s'", source_name, target_name)
        return True
    except Exception as exc:
        logger.error("Failed to create '%s': %s", source_name, exc)
        return False


def main() -> int:
    parser = argparse.ArgumentParser(description="Recreate opencode-* models with renamed identifiers")
    parser.add_argument("--dry-run", action="store_true", help="List models without creating")
    parser.add_argument("--api-key", type=str, default="", help="Override API key inline")
    args = parser.parse_args()

    if args.api_key:
        os.environ[API_KEY_ENV_VAR] = args.api_key
        logger.info("Using provided API key")

    if args.dry_run:
        print(json.dumps({"status": "dry-run-pass"}))
        return 0

    # Check which models already exist
    result = api_request("GET", "/admin/gateway/models")
    existing_models = {m["source_model"] for m in result.get("models", [])}
    logger.info("Found %d existing models", len(existing_models))

    created_count = 0
    failed_count = 0
    errors: list[str] = []

    for model in OPENCODE_MODELS:
        old_name = model["source_model"]
        new_name = old_name[len("opencode-"):]

        if new_name in existing_models:
            logger.info("Skipping '%s' - already exists", new_name)
            continue

        if create_model(new_name, model["target_model"]):
            created_count += 1
        else:
            failed_count += 1
            errors.append(f"{old_name}: creation failed")

    output = {
        "status": "success" if failed_count == 0 else "partial-failure",
        "total_opencode_models": len(OPENCODE_MODELS),
        "created": created_count,
        "failed": failed_count,
        "errors": errors,
    }
    print(json.dumps(output))
    return 0 if failed_count == 0 else 1


if __name__ == "__main__":
    sys.exit(main())

Comments

No comments yet. Start the discussion.