autoreg-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok60915d52e8425
2026-10-20 02:16:32 +03:00
commit f58eafeb48
565 changed files with 60064 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
"""
plane.so FULL COMBO - autoreg + workspace + API token gateway, end-to-end automated
====================================================================================
One account in ~20s, zero captcha. Pipeline per account:
1. POST /auth/email-check/ -> confirm email free (MAGIC_CODE flow)
2. POST /auth/magic-generate/ -> 6-digit code sent to mailbox
3. IMAP poll -> read code from SUBJECT "Plane login code is NNNNNN"
4. GET /auth/get-csrf-token/ -> csrftoken (mandatory, else CSRF failure page)
5. POST /auth/magic-sign-up/ -> 302 + session-id cookie (account created, email auto-verified)
6. GET /api/users/me/ -> verify session
7. POST /api/workspaces/ -> create workspace (name+slug)
8. POST /api/users/api-tokens/ -> issue API token (plane_api_*, 60/min, shown ONCE)
9. GET /api/v1/users/me/ (X-API-Key) -> verify token live
10. append full record to accounts.jsonl
Backend: api.plane.so (app.plane.so = SPA only, 405 on /auth).
Throttle: auth 10/min per IP -> bulk sleeps 7s.
Reverse source: github.com/makeplane/plane (preview branch):
apps/api/plane/authentication/urls.py, views/app/magic.py, app/views/api.py
CONFIG: fill IMAP_* + EMAIL_* below, or pass --emails file (email:imap_host:imap_user:imap_pass).
Usage:
python plane_full_combo.py <alias> # one account via +alias
python plane_full_combo.py --bulk N [out.jsonl] # mass run
python plane_full_combo.py --emails list.txt [out.jsonl]
Token use afterwards:
curl https://api.plane.so/api/v1/users/me/ -H "X-API-Key: [REDACTED]"
workspace-scoped: https://api.plane.so/api/v1/workspaces/<slug>/projects/ etc.
"""
import imaplib, email as emaillib, email.utils, re, time, json, sys, random, ssl
import urllib.request, urllib.error, http.cookiejar
from urllib.request import Request, urlopen
from urllib.parse import urlencode
# ============ CONFIG - FILL YOUR OWN ============
IMAP_HOST = "" # imap.gmail.com / outlook.office365.com / ...
IMAP_USER = ""
IMAP_PASS = "" # app password recommended
EMAIL_DOMAIN = "" # gmail.com
EMAIL_LOCAL = "" # yourname -> yourname+prNNNNNN@gmail.com
WORKSPACE_NAME = "farm lab"
WORKSPACE_SLUG_PREFIX = "farmlab"
# ================================================
API = "https://api.plane.so"
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Origin": "https://app.plane.so", "Referer": "https://app.plane.so/sign-up/"}
def post_json(path, payload, cookie=None, csrf=None, extra=None):
hdrs = {"Content-Type": "application/json", **UA}
if cookie: hdrs["Cookie"] = cookie
if csrf: hdrs["X-CSRFToken"] = csrf
if extra: hdrs.update(extra)
req = Request(API + path, data=json.dumps(payload).encode(), headers=hdrs)
try:
with urlopen(req, timeout=30) as r:
return r.status, json.loads(r.read().decode() or "{}")
except urllib.error.HTTPError as e:
try: body = json.loads(e.read().decode() or "{}")
except Exception: body = {}
return e.code, body
def get_csrf():
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
req = urllib.request.Request(API + "/auth/get-csrf-token/", headers=UA)
with opener.open(req, timeout=30) as r:
r.read()
token = [REDACTED] for c in cj if c.name == "csrftoken"), None)
return token, "; ".join(f"{c.name}={c.value}" for c in cj)
def post_form(path, data, cookies=None, csrf=None):
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *a, **k): return None
opener = urllib.request.build_opener(NoRedirect)
hdrs = {"Content-Type": "application/x-www-form-urlencoded", **UA}
if cookies: hdrs["Cookie"] = cookies
if csrf: hdrs["X-CSRFToken"] = csrf
req = urllib.request.Request(API + path, data=urlencode(data).encode(), headers=hdrs)
try:
r = opener.open(req, timeout=30)
return r.status, r.read().decode(errors="ignore"), r.headers
except urllib.error.HTTPError as e:
return e.code, e.read().decode(errors="ignore"), e.headers
def get_json(path, cookie=None, api_key=None):
hdrs = {"User-Agent": UA["User-Agent"]}
if cookie: hdrs["Cookie"] = cookie
if api_key: hdrs["X-API-Key"] = api_key
req = Request(API + path, headers=hdrs)
try:
with urlopen(req, timeout=30) as r:
return r.status, json.loads(r.read().decode() or "{}")
except urllib.error.HTTPError as e:
return e.code, {}
def read_otp(match_suffix, host, user, passwd, tries=24, wait=5, since_ts=None):
for _ in range(tries):
try:
m = imaplib.IMAP4_SSL(host, ssl_context=ssl.create_default_context())
m.login(user, passwd); m.select("INBOX")
_, data = m.search(None, 'SUBJECT "Plane login code"')
best = None
for num in data[0].split()[-10:]:
_, msg = m.fetch(num, "(RFC822)")
raw = msg[0][1]
try:
mime = emaillib.message_from_bytes(raw)
full = ""
for part in mime.walk():
if part.get_content_type() in ("text/plain", "text/html"):
try: full += part.get_payload(decode=True).decode(errors="ignore")
except Exception: pass
full += " " + str(mime.get("Subject", "")) + " " + str(mime.get("To", ""))
except Exception:
full = raw.decode(errors="ignore")
if match_suffix.lower() not in full.lower():
continue
mm = re.search(r"login code is (\d{6})", full)
dm = re.search(r"^Date: (.+)$", raw.decode(errors="ignore"), re.M)
ts = 0
if dm:
try: ts = email.utils.mktime_tz(email.utils.parsedate_tz(dm.group(1)))
except Exception: ts = 0
if mm and (best is None or ts >= best[0]):
best = (ts, mm.group(1))
m.logout()
if best and (since_ts is None or best[0] >= since_ts - 180):
return best[1]
except Exception as e:
print("imap err:", e, file=sys.stderr)
time.sleep(wait)
return None
def full_combo(email_addr, match_suffix, imap_host, imap_user, imap_pass, slug=None):
rec = {"email": email_addr, "ts": int(time.time())}
# 1-2. email check + magic generate
st, js = post_json("/auth/email-check/", {"email": email_addr})
if js.get("existing"):
rec["result"] = "EXISTS"; return rec
t0 = time.time()
st, js = post_json("/auth/magic-generate/", {"email": email_addr})
if st != 200:
rec["result"] = f"GEN_FAIL_{st}"; return rec
# 3. OTP
code = read_otp(match_suffix, imap_host, imap_user, imap_pass, since_ts=t0)
if not code:
rec["result"] = "NO_OTP"; return rec
# 4-5. csrf + sign-up
csrf_tok, csrf_cookie = get_csrf()
st, body, hdrs = post_form("/auth/magic-sign-up/", {"email": email_addr, "code": code},
cookies=csrf_cookie, csrf=csrf_tok)
loc = hdrs.get("Location", "")
if "error_code" in loc:
rec["result"] = "SIGNUP_ERR:" + loc[:150]; return rec
cookies = hdrs.get_all("Set-Cookie") or []
sess = "; ".join(c.split(";")[0] for c in cookies)
cookie = ((sess + "; ") if sess else "") + csrf_cookie
csrf_val = next((c.split(";")[0].split("=", 1)[1] for c in cookies if c.startswith("csrftoken=")), csrf_tok)
# 6. verify session
st, me = get_json("/api/users/me/", cookie=cookie)
if st != 200 or not me.get("email"):
rec["result"] = f"SESSION_FAIL_{st}"; return rec
rec["user_id"] = me["id"]
# 7. create workspace
slug = slug or (WORKSPACE_SLUG_PREFIX + str(random.randint(10000, 99999)))
st, ws = post_json("/api/workspaces/", {"name": WORKSPACE_NAME, "slug": slug},
cookie=cookie, csrf=csrf_val,
extra={"Referer": "https://app.plane.so/"})
if st not in (200, 201):
rec["result"] = f"WS_FAIL_{st}:{str(ws)[:100]}"; rec["session_cookie"] = cookie; return rec
rec["workspace"] = {"slug": ws.get("slug"), "id": ws.get("id")}
# 8. API token
st, tk = post_json("/api/users/api-tokens/", {"label": "combo-" + slug, "description": "farm"},
cookie=cookie, csrf=csrf_val,
extra={"Referer": "https://app.plane.so/"})
if st not in (200, 201) or not tk.get("token"):
rec["result"] = f"TOKEN_FAIL_{st}:{str(tk)[:100]}"; rec["session_cookie"] = cookie; return rec
rec["api_token"] = tk["token"]
rec["rate_limit"] = tk.get("allowed_rate_limit")
# 9. verify token on public v1
st, v1 = get_json("/api/v1/users/me/", api_key=tk["token"])
rec["token_live"] = (st == 200)
rec["session_cookie"] = cookie
rec["result"] = "FULL_OK" if st == 200 else "TOKEN_UNVERIFIED"
return rec
def gen_alias_email():
alias = "pr" + str(random.randint(100000, 999999))
return f"{EMAIL_LOCAL}+{alias}@{EMAIL_DOMAIN}", alias
def run_one(target, imap_host=None, imap_user=None, imap_pass=None):
h = imap_host or IMAP_HOST; u = imap_user or IMAP_USER; p = imap_pass or IMAP_PASS
if "@" in target:
addr, suffix = target, target
else:
if not (EMAIL_LOCAL and EMAIL_DOMAIN):
print("ERROR: fill EMAIL_LOCAL/EMAIL_DOMAIN or pass full email"); sys.exit(1)
addr = f"{EMAIL_LOCAL}+{target}@{EMAIL_DOMAIN}"; suffix = target
rec = full_combo(addr, suffix, h, u, p)
print(json.dumps(rec, indent=2, default=str))
return rec
def main():
args = sys.argv[1:]
if not args:
print(__doc__); sys.exit(1)
if args[0] == "--bulk":
n = int(args[1]) if len(args) > 1 else 10
outfile = args[2] if len(args) > 2 else "plane_accounts.jsonl"
ok = 0
with open(outfile, "a", encoding="utf-8") as f:
for i in range(n):
addr, alias = gen_alias_email()
try:
rec = full_combo(addr, alias, IMAP_HOST, IMAP_USER, IMAP_PASS)
except Exception as e:
rec = {"email": addr, "result": "EXC:" + str(e)}
f.write(json.dumps(rec, default=str) + "\n"); f.flush()
if rec.get("result") == "FULL_OK": ok += 1
print(f"[{i+1}/{n}] {addr} -> {rec.get('result')} token={rec.get('api_token','-')[:20]} (full_ok={ok})")
time.sleep(7)
print(f"DONE {ok}/{n} -> {outfile}")
elif args[0] == "--emails":
outfile = args[2] if len(args) > 2 else "plane_accounts.jsonl"
ok = 0
with open(args[1], encoding="utf-8") as f, open(outfile, "a", encoding="utf-8") as o:
for i, line in enumerate(f):
line = line.strip()
if not line: continue
parts = line.split(":")
if len(parts) == 4:
addr, h, u, p = parts
else:
addr, h, u, p = line, IMAP_HOST, IMAP_USER, IMAP_PASS
try:
rec = full_combo(addr, addr, h, u, p)
except Exception as e:
rec = {"email": addr, "result": "EXC:" + str(e)}
o.write(json.dumps(rec, default=str) + "\n"); o.flush()
if rec.get("result") == "FULL_OK": ok += 1
print(f"[{i+1}] {addr} -> {rec.get('result')} (full_ok={ok})")
time.sleep(7)
print(f"DONE {ok} -> {outfile}")
else:
run_one(args[0])
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,475 @@
# -*- coding: utf-8 -*-
"""
plane_gw.py v2 - OpenAI-compatible gateway over Plane AI (pi.plane.so)
=======================================================================
9Router-style: account pool, round-robin, auto-disable dead accounts, local-no-auth.
NEW in v2:
- TRUE token streaming (SSE delta passthrough, chunked transfer)
- OpenAI tool-calling EMULATION: upstream pi.plane.so accepts NO client tools
(tools are server-side: execute-action/artifacts/mcp). The gateway injects tool
schemas into the prompt with a strict JSON protocol, parses model output, and
returns real OpenAI tool_calls. role=tool results are folded back into the
next prompt (full transcript per turn - stateless, reliable).
- system prompts, multi-turn history, thinking/websearch via model params
Endpoints: GET /v1/models | POST /v1/chat/completions (stream+sync) | /health
/admin/pool | /admin/reload
Pool file: plane_accounts.jsonl (FULL_OK records from plane_full_combo.py)
Run: python plane_gw.py [port] (default 20130)
CLI: base_url=http://127.0.0.1:20130/v1 api_key=any model=plane/claude-sonnet-5
Env: PLANE_ACCOUNTS=<path> GW_KEY=<bearer key for remote access>
"""
import json, os, sys, time, re, threading, hashlib
import queue as pyqueue
import urllib.request, urllib.error
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PI = "https://pi.plane.so"
ACCOUNTS_FILE = os.getenv("PLANE_ACCOUNTS", os.path.join(os.path.dirname(os.path.abspath(__file__)), "plane_accounts.jsonl"))
GWKEY = os.getenv("GW_KEY", "")
POOL_LOCK = threading.Lock()
TOOL_PREAMBLE = """TOOL PROTOCOL:
You have access to these tools:
%s
When you want to call a tool, reply with ONLY a fenced json block in EXACTLY this format:
```json
{"tool_calls": [{"name": "<tool_name>", "arguments": {<json arguments per schema>}}]}
```
Nothing else in the message. You may request several tools in the list.
After the user returns tool results (lines starting with "Tool <name> result:"), continue: call another tool the same way, or give your FINAL answer in plain text (no json block).
"""
class Account:
def __init__(self, rec):
self.email = rec.get("email")
self.cookie = rec.get("session_cookie", "")
ws = rec.get("workspace") or {}
self.ws_slug = ws.get("slug")
self.ws_id = ws.get("id")
self.user_id = rec.get("user_id")
self.is_active = bool(self.cookie and self.ws_slug and self.ws_id)
self.used = 0
self.last_err = ""
self.disabled_reason = ""
class Pool:
def __init__(self, path):
self.path = path
self.accounts = []
self.idx = 0
self.reload()
def reload(self):
accs = []
if os.path.exists(self.path):
with open(self.path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line: continue
try: rec = json.loads(line)
except Exception: continue
if rec.get("result") == "FULL_OK": accs.append(Account(rec))
with POOL_LOCK:
self.accounts = accs
self.idx = 0
return len(accs)
def next(self):
with POOL_LOCK:
live = [a for a in self.accounts if a.is_active]
if not live: return None
a = live[self.idx % len(live)]
self.idx += 1
return a
def disable(self, acc, reason):
with POOL_LOCK:
acc.is_active = False
acc.disabled_reason = reason
def state(self):
with POOL_LOCK:
return [{"email": a.email, "active": a.is_active, "used": a.used,
"err": a.last_err, "disabled": a.disabled_reason} for a in self.accounts]
POOL = Pool(ACCOUNTS_FILE)
MODELS_CACHE = {"ts": 0, "models": []}
# ---------------- upstream ----------------
def _pi_request(path, acc, data=None, timeout=60):
url = PI + path
hdrs = {"Cookie": acc.cookie, "User-Agent": "Mozilla/5.0",
"Origin": "https://app.plane.so", "Referer": "https://app.plane.so/"}
body = None
if data is not None:
hdrs["Content-Type"] = "application/json"
body = json.dumps(data).encode()
req = urllib.request.Request(url, data=body, headers=hdrs,
method="POST" if data is not None else "GET")
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status, r.read().decode(errors="ignore")
def get_models(acc):
now = time.time()
if MODELS_CACHE["models"] and now - MODELS_CACHE["ts"] < 3600:
return MODELS_CACHE["models"]
try:
st, body = _pi_request("/api/v1/chat/get-models/?workspace_slug=" + str(acc.ws_slug), acc)
js = json.loads(body)
models = [m for m in js.get("models", []) if m.get("type") == "language_model"]
if models:
MODELS_CACHE["ts"] = now
MODELS_CACHE["models"] = models
return models
except Exception:
return MODELS_CACHE["models"]
def _start_stream(acc, prompt, model):
st, body = _pi_request("/api/v1/chat/initialize-chat/", acc,
{"workspace_in_context": False, "workspace_slug": acc.ws_slug})
chat_id = json.loads(body).get("chat_id")
if not chat_id:
raise RuntimeError("init_chat %s: %s" % (st, body[:150]))
payload = {"query": prompt, "llm": model, "is_new": True, "is_temp": True,
"workspace_in_context": False, "mode": "ask",
"workspace_id": acc.ws_id, "workspace_slug": acc.ws_slug,
"chat_id": chat_id, "context": {}}
st, body = _pi_request("/api/v1/chat/queue-answer/", acc, payload)
tok = json.loads(body).get("stream_token")
if not tok:
raise RuntimeError("queue %s: %s" % (st, body[:150]))
acc.used += 1
return tok
def _iter_sse(tok, acc, timeout=180):
"""Yield raw text chunks from the upstream SSE (delta events only)."""
url = PI + "/api/v1/chat/stream-answer/" + tok
req = urllib.request.Request(url, headers={"Cookie": acc.cookie, "User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=timeout) as r:
event = None
for raw in r:
line = raw.decode(errors="ignore").rstrip("\n")
if line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
data = line[5:].strip()
if event == "delta":
try:
chunk = json.loads(data).get("chunk", "")
if chunk: yield chunk
except Exception: pass
elif event == "error":
raise RuntimeError("stream error: " + data[:200])
elif event == "done":
break
def plane_chat(acc, prompt, model, timeout=180):
tok = _start_stream(acc, prompt, model)
return "".join(_iter_sse(tok, acc, timeout))
# ---------------- OpenAI request -> Plane prompt ----------------
def _msg_text(m):
c = m.get("content", "")
if isinstance(c, list):
return " ".join(str(x.get("text", "")) for x in c if isinstance(x, dict))
if c is None:
# assistant tool_calls message
calls = m.get("tool_calls") or []
return "Assistant requested tool calls: " + json.dumps(
[{"name": (t.get("function") or {}).get("name"),
"arguments": (t.get("function") or {}).get("arguments")} for t in calls])
return str(c)
def build_prompt(req):
msgs = req.get("messages", [])
tools = req.get("tools") or []
sys_parts, conv = [], []
for m in msgs:
role = m.get("role", "user")
text = _msg_text(m)
if role == "system":
sys_parts.append(text)
elif role == "tool":
name = m.get("name", "tool")
conv.append("Tool %s result: %s" % (name, text))
elif role == "assistant":
tc = m.get("tool_calls")
if tc:
brief = [{"name": (t.get("function") or {}).get("name"),
"arguments": (t.get("function") or {}).get("arguments")} for t in tc]
conv.append("Assistant: " + json.dumps({"tool_calls": brief}))
elif text:
conv.append("Assistant: " + text)
else:
conv.append("User: " + text)
prompt = ""
if sys_parts:
prompt += "System instructions:\n" + "\n".join(sys_parts) + "\n\n"
if tools:
schemas = []
for t in tools:
fn = t.get("function", t)
schemas.append(json.dumps({"name": fn.get("name"), "description": fn.get("description", ""),
"parameters": fn.get("parameters", {})}, ensure_ascii=False))
prompt += TOOL_PREAMBLE % ("\n".join(schemas)) + "\n"
prompt += "Conversation:\n" + "\n".join(conv) + "\n\nAssistant:"
return prompt
# ---------------- tool-call parsing ----------------
TOOL_BLOCK = re.compile(r"```(?:json)?\s*(\{.*?\})\s*```", re.S)
def parse_tool_calls(text):
"""Return (clean_text, [calls]) - calls extracted from fenced json blocks."""
calls, clean = [], text
for mm in list(TOOL_BLOCK.finditer(text)):
try:
js = json.loads(mm.group(1))
except Exception:
continue
if isinstance(js, dict) and isinstance(js.get("tool_calls"), list) and js["tool_calls"]:
ok = True
for c in js["tool_calls"]:
if not isinstance(c, dict) or "name" not in c:
ok = False; break
if ok:
calls.extend(js["tool_calls"])
clean = clean.replace(mm.group(0), "")
return clean.strip(), calls
class StreamToolFilter:
"""Incremental filter: pass through normal text, hold potential tool blocks."""
def __init__(self):
self.buf = ""
self.in_fence = False
self.fence_buf = ""
self.calls = []
self.clean_parts = []
def feed(self, chunk):
"""Return text safe to emit now."""
out = []
self.buf += chunk
while True:
if not self.in_fence:
idx = self.buf.find("```")
if idx == -1:
keep = max(0, len(self.buf) - 3) # hold back possible partial fence
if keep:
out.append(self.buf[:keep]); self.clean_parts.append(self.buf[:keep])
self.buf = self.buf[keep:]
break
else:
if idx:
out.append(self.buf[:idx]); self.clean_parts.append(self.buf[:idx])
self.in_fence = True
self.fence_buf = ""
self.buf = self.buf[idx+3:]
else:
end = self.buf.find("```")
if end == -1:
self.fence_buf += self.buf; self.buf = ""
if len(self.fence_buf) > 20000: # runaway fence = not a tool block
out.append("```" + self.fence_buf); self.clean_parts.append("```" + self.fence_buf)
self.fence_buf = ""; self.in_fence = False
break
else:
block = self.fence_buf + self.buf[:end]
self.buf = self.buf[end+3:]
self.in_fence = False
js = None
try:
js = json.loads(block.strip())
is_tool = isinstance(js, dict) and isinstance(js.get("tool_calls"), list) and js["tool_calls"] \
and all(isinstance(c, dict) and "name" in c for c in js["tool_calls"])
except Exception:
is_tool = False
if is_tool:
self.calls.extend(js["tool_calls"])
else:
txt = "```" + block + "```"
out.append(txt); self.clean_parts.append(txt)
return "".join(out)
def finish(self):
"""Flush held-back content at stream end. Returns (final_clean_text, calls)."""
tail = ""
if self.in_fence:
tail = "```" + self.fence_buf
tail += self.buf
if tail:
self.clean_parts.append(tail)
return "".join(self.clean_parts).strip(), self.calls
# ---------------- streaming worker ----------------
def plane_stream_worker(acc, prompt, model, out_q, timeout=180):
try:
tok = _start_stream(acc, prompt, model)
flt = StreamToolFilter()
for chunk in _iter_sse(tok, acc, timeout):
emit = flt.feed(chunk)
if emit: out_q.put(("delta", emit))
clean, calls = flt.finish()
out_q.put(("final", (clean, calls)))
except Exception as e:
out_q.put(("err", str(e)[:200]))
out_q.put(("end", None))
# ---------------- HTTP handler ----------------
def strip_model_prefix(m):
return m.split("/", 1)[1] if m.startswith("plane/") else m
def mk_tool_calls_message(calls, model):
tcs = []
for i, c in enumerate(calls):
args = c.get("arguments", {})
if not isinstance(args, str): args = json.dumps(args, ensure_ascii=False)
tcs.append({"id": "call_plane_%d_%d" % (int(time.time()), i), "type": "function",
"function": {"name": c.get("name", ""), "arguments": args}})
return {"role": "assistant", "content": None, "tool_calls": tcs}
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def log_message(self, *a): pass
def _local(self): return self.client_address[0] in ("127.0.0.1", "::1")
def _authed(self):
if not GWKEY: return True
if self._local(): return True
auth = self.headers.get("Authorization", "")
if auth.startswith("Bearer ") and auth[7:] == GWKEY: return True
if self.headers.get("x-api-key") == GWKEY: return True
return False
def _send(self, code, obj):
data = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(data)
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Headers", "*")
self.send_header("Access-Control-Allow-Methods", "*")
self.send_header("Content-Length", "0")
self.end_headers()
def do_GET(self):
path = self.path.split("?")[0]
if path == "/health":
return self._send(200, {"ok": True, "pool_live": len([a for a in POOL.accounts if a.is_active]), "v": 2})
if path == "/admin/pool":
if not self._local(): return self._send(403, {"error": "local only"})
return self._send(200, POOL.state())
if path == "/admin/reload":
if not self._local(): return self._send(403, {"error": "local only"})
return self._send(200, {"loaded": POOL.reload()})
if not self._authed():
return self._send(401, {"error": {"message": "API key required for remote API access", "type": "auth"}})
if path == "/v1/models":
acc = POOL.next()
models = get_models(acc) if acc else []
data = [{"id": "plane/" + m["id"], "object": "model", "created": int(time.time()),
"owned_by": m.get("provider", "plane")} for m in models]
return self._send(200, {"object": "list", "data": data})
return self._send(404, {"error": "not found"})
def do_POST(self):
path = self.path.split("?")[0]
if path != "/v1/chat/completions":
return self._send(404, {"error": "not found"})
if not self._authed():
return self._send(401, {"error": {"message": "API key required for remote API access", "type": "auth"}})
n = int(self.headers.get("Content-Length", 0))
try:
req = json.loads(self.rfile.read(n) or b"{}")
except Exception:
return self._send(400, {"error": "bad json"})
model = strip_model_prefix(req.get("model", "claude-sonnet-5"))
stream = bool(req.get("stream"))
prompt = build_prompt(req)
tried, last_err = 0, ""
while tried < 3:
acc = POOL.next()
if acc is None:
return self._send(503, {"error": {"message": "No active accounts in pool", "type": "pool"}})
tried += 1
try:
if stream:
return self._stream_response(acc, prompt, model)
text = plane_chat(acc, prompt, model)
clean, calls = parse_tool_calls(text)
msg = mk_tool_calls_message(calls, model) if calls else {"role": "assistant", "content": clean}
resp = {"id": "chatcmpl-plane-" + hashlib.sha1(prompt.encode()).hexdigest()[:12],
"object": "chat.completion", "created": int(time.time()), "model": model,
"choices": [{"index": 0, "message": msg,
"finish_reason": "tool_calls" if calls else "stop"}],
"usage": {"prompt_tokens": len(prompt)//4, "completion_tokens": len(text)//4,
"total_tokens": (len(prompt)+len(text))//4}}
return self._send(200, resp)
except urllib.error.HTTPError as e:
last_err = "HTTP %s" % e.code
acc.last_err = last_err
if e.code in (401, 403): POOL.disable(acc, "auth %s" % e.code)
elif e.code == 429: POOL.disable(acc, "credits/ratelimit 429")
except Exception as e:
last_err = str(e)[:150]
acc.last_err = last_err
return self._send(502, {"error": {"message": "upstream failed: " + last_err, "type": "upstream"}})
def _stream_response(self, acc, prompt, model):
q = pyqueue.Queue()
t = threading.Thread(target=plane_stream_worker, args=(acc, prompt, model, q), daemon=True)
t.start()
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
cid = "chatcmpl-plane-" + hashlib.sha1((prompt + str(time.time())).encode()).hexdigest()[:12]
def send_chunk(payload):
b = payload.encode()
self.wfile.write(("%X\r\n" % len(b)).encode() + b + b"\r\n")
self.wfile.flush()
def base(delta, finish=None):
return {"id": cid, "object": "chat.completion.chunk", "created": int(time.time()),
"model": model, "choices": [{"index": 0, "delta": delta, "finish_reason": finish}]}
send_chunk("data: " + json.dumps(base({"role": "assistant", "content": ""})) + "\n\n")
calls = []
while True:
kind, payload = q.get()
if kind == "delta":
send_chunk("data: " + json.dumps(base({"content": payload})) + "\n\n")
elif kind == "final":
clean, calls = payload
if calls:
for i, c in enumerate(calls):
args = c.get("arguments", {})
if not isinstance(args, str): args = json.dumps(args, ensure_ascii=False)
d = {"tool_calls": [{"index": i, "id": "call_plane_%d_%d" % (int(time.time()), i),
"type": "function",
"function": {"name": c.get("name", ""), "arguments": args}}]}
send_chunk("data: " + json.dumps(base(d)) + "\n\n")
elif kind == "err":
send_chunk("data: " + json.dumps({"error": {"message": payload}}) + "\n\n")
elif kind == "end":
break
finish = "tool_calls" if calls else "stop"
try:
send_chunk("data: " + json.dumps(base({}, finish)) + "\n\n")
send_chunk("data: [DONE]\n\n")
self.wfile.write(b"0\r\n\r\n")
self.wfile.flush()
except Exception:
pass
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 20130
n = POOL.reload()
print("[plane_gw v2] pool: %d accounts from %s" % (n, ACCOUNTS_FILE))
print("[plane_gw v2] listening on http://127.0.0.1:%d/v1 (auth=%s, tools=emulated, streaming=true)" % (port, "on" if GWKEY else "off"))
ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
if __name__ == "__main__":
main()