141 lines
5.2 KiB
Python
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()
|