Files
hack/tools/scripts/revive_channels.py
T

141 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Revive disabled-but-live channels from reconcile.json by DELETE + RECREATE.
The NewAPI fork's PUT cannot change `status` (rejected as "Invalid parameters")
and the batch endpoint DELETES by default, so the reliable way to revive a
channel is to delete it and recreate with the correct config.
Pulls the real key from the vault (matched by name suffix) and sets test_model
to the model that direct verification found working.
"""
import json, re, sys, time
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.insert(0, str(Path(__file__).resolve().parent))
import newapi_client as n
import importlib.util
spec = importlib.util.spec_from_file_location(
"r", str(Path(__file__).resolve().parent / "reconcile_newapi.py"))
R = importlib.util.module_from_spec(spec); spec.loader.exec_module(R)
H = Path(__file__).resolve().parent / "llm-key-hunter"
report = json.loads((H / "results/reconcile.json").read_text())
chans = {c["id"]: c for c in json.loads(Path("/tmp/chans.json").read_text())}
all_keys = R.load_all_keys()
def match_key(name, prov):
"""Find vault/bucket key matching a channel name suffix (last4 or last6)."""
suf = name.rsplit("_", 1)[-1].lower().split("-")[0]
for k in all_keys:
if k["prov"] != prov:
continue
kl = k["key"].lower()
if len(suf) == 4 and kl[-4:] == suf:
return k["key"]
if len(suf) >= 5 and kl[-len(suf):] == suf:
return k["key"]
# fallback: last6 ends with suf
for k in all_keys:
if k["prov"] == prov and k["key"].lower()[-6:].endswith(suf):
return k["key"]
return None
def zhipu_channel(name, key, kind, model):
if kind == "coding":
return dict(name=name, type=n.TYPE_CUSTOM, key=key,
base_url="https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
models="glm-4.7-flash,glm-4.5-air,glm-4.5-flash,glm-4-flash,glm-5.2",
test_model=model, model_mapping="", groups=["default"], group="default",
priority=0, weight=0, auto_ban=1)
return dict(name=name, type=n.TYPE_CUSTOM, key=key,
base_url="https://open.bigmodel.cn/api/paas/v4/chat/completions",
models="glm-4-flash,glm-4.5-flash",
test_model=model or "glm-4-flash", model_mapping="",
groups=["default"], group="default", priority=0, weight=0, auto_ban=1)
def volcano_coding_channel(name, key):
return dict(name=name, type=n.TYPE_ANTHROPIC, key=key,
base_url="https://ark.cn-beijing.volces.com/api/coding",
models="ark-code-latest", test_model="ark-code-latest",
model_mapping="", groups=["default"], group="default",
priority=0, weight=0, auto_ban=1)
def parse_detail(detail):
m = re.match(r"(\w+)\s+([\w./\-]+)\s+200", detail)
if m:
return m.group(1), m.group(2)
return None, None
def revive_one(rec):
prov = rec["prov"]
kind, model = parse_detail(rec["detail"])
results = []
for cid, name, st in rec["existing"]:
if st == 1:
continue
old = chans.get(cid, {})
# find real key
key = match_key(name, prov)
if not key:
# for zhipu the old suffix is 4-char; try harder
results.append((cid, name, "NO_KEY", ""))
continue
# build new channel config
if prov == "zhipu":
newch = zhipu_channel(name, key, kind, model)
elif prov == "volcanoark" and "CodingPlan" in name:
newch = volcano_coding_channel(name, key)
else:
# keep existing config but ensure test_model
newch = dict(name=name, type=old.get("type", 1), key=key,
base_url=old.get("base_url", ""),
models=old.get("models", ""), test_model=model or "",
model_mapping=old.get("model_mapping", ""),
groups=["default"], group="default",
priority=0, weight=0, auto_ban=1)
# delete + recreate
ok, msg = n.delete_channel(cid)
if not ok:
results.append((cid, name, "DEL_FAIL", msg))
continue
time.sleep(0.15)
ok, msg = n.add_channel(newch)
if not ok:
results.append((cid, name, "ADD_FAIL", msg))
continue
results.append((cid, name, "RECREATED", msg))
time.sleep(0.15)
return results
def main():
targets = report["usable_channel_disabled"]
print(f"reviving {len(targets)} disabled-live records via DELETE+RECREATE...")
done = 0; ok = 0; fail = 0
log = open(H / "results/revive.log", "w")
with ThreadPoolExecutor(max_workers=6) as ex:
futs = [ex.submit(revive_one, rec) for rec in targets]
for fut in as_completed(futs):
for cid, name, status, msg in fut.result():
line = f" {cid:4} {name:28} {status} {msg}"
print(line); log.write(line + "\n"); log.flush()
done += 1
if status == "RECREATED":
ok += 1
else:
fail += 1
if done % 20 == 0:
print(f" --- {done} processed ---")
log.close()
print(f"\n=== DONE: recreated={ok} failed={fail} ===")
if __name__ == "__main__":
main()