devplace_recreate_models.py - Recreate Models After Rename
📝 PythonRecreates 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.