@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +147 -147
- package/BENCHMARKS.md +262 -262
- package/GATEWAY.md +272 -272
- package/NOTICE +28 -28
- package/PRIVACY.md +46 -46
- package/README.md +368 -314
- package/SECURITY.md +83 -83
- package/TERMS.md +22 -22
- package/USAGE.md +170 -163
- package/VERSION +1 -1
- package/bin/benchwarmers.js +16 -74
- package/cli.py +666 -0
- package/examples/config/gateway.json +5 -5
- package/examples/config/model-roles.json +11 -11
- package/gateway.py +755 -755
- package/launcher.py +120 -0
- package/mcp_server.py +266 -266
- package/metrics/alerts.py +116 -116
- package/metrics/aliases.py +41 -41
- package/metrics/bench_collections.py +141 -141
- package/metrics/catalog.py +295 -295
- package/metrics/client.py +394 -394
- package/metrics/cliproxy.py +191 -191
- package/metrics/connect.py +691 -691
- package/metrics/cost.py +152 -152
- package/metrics/decide.py +227 -227
- package/metrics/discover.py +318 -318
- package/metrics/endpoints.py +436 -436
- package/metrics/guard.py +617 -592
- package/metrics/learn.py +221 -221
- package/metrics/litellm_hub.py +206 -206
- package/metrics/model_roles.py +84 -84
- package/metrics/offload.py +166 -166
- package/metrics/owui.py +205 -205
- package/metrics/paths.py +60 -60
- package/metrics/route.py +579 -579
- package/metrics/scheduler.py +275 -275
- package/metrics/sources.py +167 -167
- package/metrics/wrong.py +144 -144
- package/package.json +68 -65
- package/perf/README.md +89 -89
- package/perf/run.py +261 -261
- package/scripts/auto_retire.py +367 -367
- package/scripts/check.py +108 -106
- package/scripts/check_deprecations.py +378 -378
- package/scripts/check_themes.py +103 -103
- package/scripts/init.py +124 -124
- package/scripts/list_served.py +78 -78
- package/scripts/notice.py +78 -78
- package/scripts/onboard.py +401 -331
- package/scripts/provider_tags.py +129 -129
- package/scripts/reprefix.py +136 -136
- package/scripts/sync_config.py +105 -105
- package/scripts/test_auth.py +255 -255
- package/scripts/test_cli.py +189 -0
- package/scripts/test_client.py +233 -233
- package/scripts/test_connect.py +222 -222
- package/scripts/test_first_run.py +180 -180
- package/scripts/test_hubs.py +328 -328
- package/scripts/test_launcher.py +67 -0
- package/scripts/test_llm_log.py +85 -81
- package/scripts/test_mcp.py +178 -178
- package/scripts/test_merged.py +214 -214
- package/scripts/test_no_owui.py +291 -291
- package/scripts/test_owui.py +90 -90
- package/scripts/test_serve_edit.py +448 -438
- package/scripts/test_verdict.py +98 -98
- package/scripts/test_vision.py +228 -228
- package/serve.py +3904 -3764
- package/suite/mine.py +154 -154
- package/suite/pace.py +145 -145
- package/suite/run.py +1046 -1042
- package/suite/seed.py +421 -421
- package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
- package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
- package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
- package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
- package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
- package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
- package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
- package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
- package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
- package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
- package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
- package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
- package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
- package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
- package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
- package/ui/bench.css +430 -425
- package/ui/bench.js +1756 -1576
- package/ui/themes/dark.css +35 -35
- package/ui/themes/hardwood.css +52 -52
- package/ui/themes/rainforest.css +32 -32
package/scripts/test_connect.py
CHANGED
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
"""Connect, end to end through the dashboard, against a fake provider. No real files.
|
|
2
|
-
|
|
3
|
-
python scripts/test_connect.py
|
|
4
|
-
|
|
5
|
-
metrics/connect.py --selftest covers the flows themselves; this covers what
|
|
6
|
-
serve.py does with them: the callback route on the real handler, saving the
|
|
7
|
-
endpoint by save_endpoint's rules (an owner's headers kept), the refresh token
|
|
8
|
-
beside the key, a device sign-in finished by polling, Ollama's local add, and
|
|
9
|
-
models on a renewing endpoint routed to the built-in client. Endpoints, keys,
|
|
10
|
-
the change log and connect.json all point at a temp folder.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import base64
|
|
16
|
-
import hashlib
|
|
17
|
-
import json
|
|
18
|
-
import pathlib
|
|
19
|
-
import sys
|
|
20
|
-
import tempfile
|
|
21
|
-
import threading
|
|
22
|
-
import urllib.parse
|
|
23
|
-
import urllib.request
|
|
24
|
-
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
25
|
-
|
|
26
|
-
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
27
|
-
sys.path.insert(0, str(ROOT))
|
|
28
|
-
import serve # noqa: E402
|
|
29
|
-
import client # noqa: E402
|
|
30
|
-
import connect # noqa: E402
|
|
31
|
-
|
|
32
|
-
challenges: dict[str, str] = {}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class Provider(BaseHTTPRequestHandler):
|
|
36
|
-
def do_GET(self): # noqa: N802
|
|
37
|
-
self._send(200, {"data": [{"id": "m1"}, {"id": "m2"}]} if self.path == "/v1/models" else {})
|
|
38
|
-
|
|
39
|
-
def do_POST(self): # noqa: N802
|
|
40
|
-
raw = self.rfile.read(int(self.headers.get("Content-Length") or 0)).decode()
|
|
41
|
-
f = json.loads(raw) if "json" in self.headers.get("Content-Type", "") else dict(urllib.parse.parse_qsl(raw))
|
|
42
|
-
if self.path == "/device":
|
|
43
|
-
return self._send(200, {"device_code": "dc", "user_code": "WXYZ-0000",
|
|
44
|
-
"verification_uri": "https://fake/device", "interval": 0})
|
|
45
|
-
if f.get("grant_type", "").endswith("device_code"):
|
|
46
|
-
return self._send(200, {"access_token": "dev-token", "expires_in": 28800})
|
|
47
|
-
verifier = f.get("code_verifier", "")
|
|
48
|
-
if challenges.get(f.get("code", "")) != base64.urlsafe_b64encode(
|
|
49
|
-
hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode():
|
|
50
|
-
return self._send(400, {"error": "invalid_grant"})
|
|
51
|
-
if self.path == "/keys":
|
|
52
|
-
return self._send(200, {"key": "sk-fake"})
|
|
53
|
-
self._send(200, {"access_token": "at", "refresh_token": "rt", "expires_in": 3600})
|
|
54
|
-
|
|
55
|
-
def _send(self, status: int, data: dict) -> None:
|
|
56
|
-
body = json.dumps(data).encode()
|
|
57
|
-
self.send_response(status)
|
|
58
|
-
self.send_header("Content-Type", "application/json")
|
|
59
|
-
self.send_header("Content-Length", str(len(body)))
|
|
60
|
-
self.end_headers()
|
|
61
|
-
self.wfile.write(body)
|
|
62
|
-
|
|
63
|
-
def log_message(self, *_a):
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def serve_on(handler) -> ThreadingHTTPServer:
|
|
68
|
-
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
|
|
69
|
-
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
70
|
-
return server
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def main() -> int:
|
|
74
|
-
provider, dash = serve_on(Provider), serve_on(serve.Handler)
|
|
75
|
-
fake = f"http://127.0.0.1:{provider.server_port}"
|
|
76
|
-
saved = (dict(connect.PROVIDERS), connect.CONFIG, serve.PORT, serve.CHANGES_PATH,
|
|
77
|
-
serve.endpoints.FILE, serve.endpoints.YAML, serve.endpoints.LLM_DIR, serve.DATA, serve.DISMISSED_PATH)
|
|
78
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
79
|
-
t = pathlib.Path(tmp)
|
|
80
|
-
try:
|
|
81
|
-
serve.PORT = dash.server_port
|
|
82
|
-
connect.CONFIG, serve.CHANGES_PATH = t / "connect.json", t / "changes.jsonl"
|
|
83
|
-
serve.endpoints.FILE, serve.endpoints.YAML, serve.endpoints.LLM_DIR = (
|
|
84
|
-
t / "endpoints.json", t / "models.yaml", t)
|
|
85
|
-
# An owner's endpoint with its own attribution headers, which a sign-in must keep.
|
|
86
|
-
serve.endpoints.FILE.write_text(json.dumps({
|
|
87
|
-
"hub": {"base": fake + "/v1", "key": "hub", "default": True},
|
|
88
|
-
"fk": {"base": fake + "/v1", "key": "fk", "headers": {"X-Title": "owner"}}}), encoding="utf-8")
|
|
89
|
-
base = {"base": fake + "/v1", "docs": "https://x"}
|
|
90
|
-
connect.PROVIDERS.update(
|
|
91
|
-
fk={**base, "label": "Key", "flow": "key", "authorize": fake + "/auth", "token": fake + "/keys",
|
|
92
|
-
"entry": {"headers": {"X-Title": "Benchwarmers"}}},
|
|
93
|
-
fo={**base, "label": "OAuth", "flow": "oauth", "authorize": fake + "/auth", "token": fake + "/token",
|
|
94
|
-
"client_id": "cid", "entry": {"api": "responses"}},
|
|
95
|
-
fd={**base, "label": "Device", "flow": "device", "device": fake + "/device",
|
|
96
|
-
"token": fake + "/token", "client_id": "cid", "register": "an app at the fake"},
|
|
97
|
-
fl={**base, "label": "Local", "flow": "local"},
|
|
98
|
-
fc={**base, "label": "Code", "flow": "oauth", "authorize": fake + "/auth", "token": fake + "/token",
|
|
99
|
-
"device": fake + "/device", "client_id": "cid"})
|
|
100
|
-
|
|
101
|
-
def callback(pid: str, endpoint: str = "") -> str:
|
|
102
|
-
r = serve.connect_start(pid, endpoint)
|
|
103
|
-
assert r["ok"], r
|
|
104
|
-
q = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(r["url"]).query))
|
|
105
|
-
back = q.get("callback_url") or q.get("redirect_uri")
|
|
106
|
-
assert f":{dash.server_port}/connect/callback" in back, q
|
|
107
|
-
challenges["code-" + pid] = q["code_challenge"]
|
|
108
|
-
# The key flow is sent back WITHOUT state, as OpenRouter may do:
|
|
109
|
-
# the id in the callback's path is what finds the sign-in.
|
|
110
|
-
url = (back + "?code=code-" + pid) if "callback_url" in q else \
|
|
111
|
-
back + "?" + urllib.parse.urlencode({"state": q["state"], "code": "code-" + pid})
|
|
112
|
-
url = url.replace("localhost", "127.0.0.1")
|
|
113
|
-
page = urllib.request.urlopen(url, timeout=10).read().decode()
|
|
114
|
-
assert serve.connect_poll(r["id"])["ok"], pid # the dashboard tab hears it
|
|
115
|
-
return page
|
|
116
|
-
|
|
117
|
-
# A provider can be several endpoints: "fk" is taken (the owner's, by
|
|
118
|
-
# key), so a sign-in adds "fk-2" and leaves "fk" exactly as it was.
|
|
119
|
-
before = serve.endpoints.load()["fk"]
|
|
120
|
-
page = callback("fk")
|
|
121
|
-
assert "Key is connected as fk-2" in page and "lists 2 models" in page, page
|
|
122
|
-
data, keys = serve.endpoints.load(), serve.endpoints.keys()
|
|
123
|
-
assert data["fk"] == before and "fk" not in keys, data["fk"]
|
|
124
|
-
assert data["fk-2"]["headers"] == {"X-Title": "Benchwarmers"}, data["fk-2"]
|
|
125
|
-
assert keys["fk-2"] == "sk-fake" and data["fk-2"]["connect"]["provider"] == "fk", (keys, data)
|
|
126
|
-
assert "sk-fake" not in page and "sk-fake" not in serve.CHANGES_PATH.read_text(encoding="utf-8")
|
|
127
|
-
# Signing that one in again keeps its name and its own headers.
|
|
128
|
-
connect.locked.update(serve.endpoints.FILE, lambda d: d["fk-2"].update(headers={"X-Title": "mine"}))
|
|
129
|
-
assert "connected as fk-2" in callback("fk", "fk-2")
|
|
130
|
-
assert serve.endpoints.load()["fk-2"]["headers"] == {"X-Title": "mine"}
|
|
131
|
-
assert not serve.connect_start("fk", "fk")["ok"] # never signed in: not its button
|
|
132
|
-
# Add refuses a name in use and offers a free one; Change key edits it.
|
|
133
|
-
refused = serve.save_endpoint("fk", fake + "/v1", "k", new=True)
|
|
134
|
-
assert not refused["ok"] and "fk-3" in refused["errors"][0], refused
|
|
135
|
-
# A sign-in can be given its own name instead of the next number...
|
|
136
|
-
assert "connected as work-or" in callback("fk", "work-or")
|
|
137
|
-
# ...but not one in use, one that is not a name, or a key the store already holds.
|
|
138
|
-
serve.endpoints.set_key("someones", "theirs")
|
|
139
|
-
for name in ("hub", "Bad Name", "someones"):
|
|
140
|
-
assert not serve.connect_start("fk", name)["ok"], name
|
|
141
|
-
assert serve.endpoints.keys()["someones"] == "theirs"
|
|
142
|
-
assert {p["id"]: p for p in serve.connect_list()["providers"]}["fk"]["suggest"] == "fk-3"
|
|
143
|
-
|
|
144
|
-
page = callback("fo")
|
|
145
|
-
data, keys = serve.endpoints.load(), serve.endpoints.keys()
|
|
146
|
-
assert keys["fo"] == "at" and keys["fo.refresh"] == "rt", keys
|
|
147
|
-
assert data["fo"]["connect"]["refreshes"] and data["fo"]["api"] == "responses", data["fo"]
|
|
148
|
-
serve.endpoints.YAML.write_text(serve.endpoints.yaml_block("fo-m1", "m1", "fo")
|
|
149
|
-
+ serve.endpoints.yaml_block("fk-m1", "m1", "fk"), encoding="utf-8")
|
|
150
|
-
assert client.backend("fo-m1") == "builtin", "a renewing endpoint must not go through llm"
|
|
151
|
-
assert client.backend("fk-m1") == client.backend() # a plain key: as configured
|
|
152
|
-
|
|
153
|
-
bad = urllib.request.urlopen(f"http://127.0.0.1:{dash.server_port}/connect/callback?state=nope&code=x",
|
|
154
|
-
timeout=10).read().decode()
|
|
155
|
-
assert "Not connected" in bad and "unknown or has expired" in bad, bad
|
|
156
|
-
|
|
157
|
-
d = serve.connect_start("fd")
|
|
158
|
-
assert d["ok"] and d["code"] == "WXYZ-0000", d
|
|
159
|
-
done = serve.connect_poll(d["id"])
|
|
160
|
-
assert done["ok"] and done["endpoint"] == "fd" and serve.endpoints.keys()["fd"] == "dev-token", done
|
|
161
|
-
|
|
162
|
-
local = serve.connect_start("fl")
|
|
163
|
-
assert local["ok"] and serve.endpoints.load()["fl"]["base"] == fake + "/v1", local
|
|
164
|
-
assert serve.connect_list_suggest("fl") == "fl" # adding it again refreshes it
|
|
165
|
-
|
|
166
|
-
# Use your own app: this install's client id instead of the shipped one, and back.
|
|
167
|
-
assert serve.connect_app("fd", "Iv1.own")["ok"]
|
|
168
|
-
row = {p["id"]: p for p in serve.connect_list()["providers"]}["fd"]
|
|
169
|
-
assert row["own_id"] == "Iv1.own" and row["shipped"] and connect.client_id("fd") == "Iv1.own", row
|
|
170
|
-
assert serve.connect_app("fd", "")["ok"] and connect.client_id("fd") == "cid"
|
|
171
|
-
assert not serve.connect_app("fk", "x-id")["ok"] # nothing to register for it
|
|
172
|
-
|
|
173
|
-
# A key typed over a sign-in replaces it, or the stale refresh would overwrite it.
|
|
174
|
-
assert serve.save_endpoint("fo", fake + "/v1", "pasted")["ok"]
|
|
175
|
-
assert "connect" not in serve.endpoints.load()["fo"] and serve.endpoints.keys()["fo"] == "pasted"
|
|
176
|
-
assert client.backend("fo-m1") == "builtin" # still the Responses API
|
|
177
|
-
assert not serve.connect_start("fo", "fo")["ok"] # no longer a sign-in to renew
|
|
178
|
-
callback("fo") # a new sign-in: its own endpoint
|
|
179
|
-
assert serve.endpoints.load()["fo-2"]["connect"]["provider"] == "fo"
|
|
180
|
-
|
|
181
|
-
listed = {p["id"]: p for p in serve.connect_list()["providers"]}
|
|
182
|
-
assert not listed["fk"]["missing"] and listed["fk"]["flow"] == "key", listed["fk"]
|
|
183
|
-
rows = {r["name"]: r for r in serve.endpoint_list()["endpoints"]}
|
|
184
|
-
assert rows["fo-2"]["connect"]["label"] == "OAuth" and rows["fo-2"]["connect"]["flow"] == "oauth"
|
|
185
|
-
assert not rows["fo-2"]["connect"]["expired"] and rows["hub"]["connect"] is None
|
|
186
|
-
|
|
187
|
-
# From a browser on another machine: by code where the provider has a device URL...
|
|
188
|
-
c = serve.connect_start("fc", "", True)
|
|
189
|
-
assert c["ok"] and c["code"] == "WXYZ-0000" and "url" not in c, c
|
|
190
|
-
assert serve.connect_poll(c["id"])["endpoint"] == "fc"
|
|
191
|
-
# ...and refused where it signs in only through a page on this machine.
|
|
192
|
-
assert not serve.connect_start("fk", "", True)["ok"]
|
|
193
|
-
assert {p["id"]: p for p in serve.connect_list()["providers"]}["fc"]["code"]
|
|
194
|
-
|
|
195
|
-
# Make default: moved, the default can't be removed, and records keyed by
|
|
196
|
-
# endpoint are renamed with it (bare on the default, served@endpoint elsewhere).
|
|
197
|
-
cfg = t / "config"
|
|
198
|
-
cfg.mkdir()
|
|
199
|
-
serve.DATA, serve.DISMISSED_PATH = t, cfg / "dismissed.json"
|
|
200
|
-
(cfg / "served-snapshot.json").write_text(json.dumps({"last_seen": {
|
|
201
|
-
"m1": "d", "m1@fk-2": "d", "a@b": "d"}, "first_missing": {"m9": "d"}}), encoding="utf-8")
|
|
202
|
-
serve.DISMISSED_PATH.write_text(json.dumps({"m2": {}, "m3@fk-2": {}}), encoding="utf-8")
|
|
203
|
-
assert serve.set_default("fk-2")["ok"] and serve.endpoints.default() == "fk-2"
|
|
204
|
-
snap = json.loads((cfg / "served-snapshot.json").read_text(encoding="utf-8"))
|
|
205
|
-
assert set(snap["last_seen"]) == {"m1@hub", "m1", "a@b@hub"}, snap # "@b" is not an endpoint
|
|
206
|
-
assert set(snap["first_missing"]) == {"m9@hub"}, snap
|
|
207
|
-
assert set(json.loads(serve.DISMISSED_PATH.read_text(encoding="utf-8"))) == {"m2@hub", "m3"}
|
|
208
|
-
assert not serve.remove_endpoint("fk-2")["ok"] and not serve.set_default("nope")["ok"]
|
|
209
|
-
assert sum(1 for e in serve.endpoints.load().values() if e.get("default")) == 1
|
|
210
|
-
finally:
|
|
211
|
-
connect.PROVIDERS.clear()
|
|
212
|
-
connect.PROVIDERS.update(saved[0])
|
|
213
|
-
(connect.CONFIG, serve.PORT, serve.CHANGES_PATH, serve.endpoints.FILE, serve.endpoints.YAML,
|
|
214
|
-
serve.endpoints.LLM_DIR, serve.DATA, serve.DISMISSED_PATH) = saved[1:]
|
|
215
|
-
provider.shutdown()
|
|
216
|
-
dash.shutdown()
|
|
217
|
-
print("ok")
|
|
218
|
-
return 0
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if __name__ == "__main__":
|
|
222
|
-
sys.exit(main())
|
|
1
|
+
"""Connect, end to end through the dashboard, against a fake provider. No real files.
|
|
2
|
+
|
|
3
|
+
python scripts/test_connect.py
|
|
4
|
+
|
|
5
|
+
metrics/connect.py --selftest covers the flows themselves; this covers what
|
|
6
|
+
serve.py does with them: the callback route on the real handler, saving the
|
|
7
|
+
endpoint by save_endpoint's rules (an owner's headers kept), the refresh token
|
|
8
|
+
beside the key, a device sign-in finished by polling, Ollama's local add, and
|
|
9
|
+
models on a renewing endpoint routed to the built-in client. Endpoints, keys,
|
|
10
|
+
the change log and connect.json all point at a temp folder.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import base64
|
|
16
|
+
import hashlib
|
|
17
|
+
import json
|
|
18
|
+
import pathlib
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import threading
|
|
22
|
+
import urllib.parse
|
|
23
|
+
import urllib.request
|
|
24
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
25
|
+
|
|
26
|
+
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
27
|
+
sys.path.insert(0, str(ROOT))
|
|
28
|
+
import serve # noqa: E402
|
|
29
|
+
import client # noqa: E402
|
|
30
|
+
import connect # noqa: E402
|
|
31
|
+
|
|
32
|
+
challenges: dict[str, str] = {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Provider(BaseHTTPRequestHandler):
|
|
36
|
+
def do_GET(self): # noqa: N802
|
|
37
|
+
self._send(200, {"data": [{"id": "m1"}, {"id": "m2"}]} if self.path == "/v1/models" else {})
|
|
38
|
+
|
|
39
|
+
def do_POST(self): # noqa: N802
|
|
40
|
+
raw = self.rfile.read(int(self.headers.get("Content-Length") or 0)).decode()
|
|
41
|
+
f = json.loads(raw) if "json" in self.headers.get("Content-Type", "") else dict(urllib.parse.parse_qsl(raw))
|
|
42
|
+
if self.path == "/device":
|
|
43
|
+
return self._send(200, {"device_code": "dc", "user_code": "WXYZ-0000",
|
|
44
|
+
"verification_uri": "https://fake/device", "interval": 0})
|
|
45
|
+
if f.get("grant_type", "").endswith("device_code"):
|
|
46
|
+
return self._send(200, {"access_token": "dev-token", "expires_in": 28800})
|
|
47
|
+
verifier = f.get("code_verifier", "")
|
|
48
|
+
if challenges.get(f.get("code", "")) != base64.urlsafe_b64encode(
|
|
49
|
+
hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode():
|
|
50
|
+
return self._send(400, {"error": "invalid_grant"})
|
|
51
|
+
if self.path == "/keys":
|
|
52
|
+
return self._send(200, {"key": "sk-fake"})
|
|
53
|
+
self._send(200, {"access_token": "at", "refresh_token": "rt", "expires_in": 3600})
|
|
54
|
+
|
|
55
|
+
def _send(self, status: int, data: dict) -> None:
|
|
56
|
+
body = json.dumps(data).encode()
|
|
57
|
+
self.send_response(status)
|
|
58
|
+
self.send_header("Content-Type", "application/json")
|
|
59
|
+
self.send_header("Content-Length", str(len(body)))
|
|
60
|
+
self.end_headers()
|
|
61
|
+
self.wfile.write(body)
|
|
62
|
+
|
|
63
|
+
def log_message(self, *_a):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def serve_on(handler) -> ThreadingHTTPServer:
|
|
68
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
|
|
69
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
70
|
+
return server
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main() -> int:
|
|
74
|
+
provider, dash = serve_on(Provider), serve_on(serve.Handler)
|
|
75
|
+
fake = f"http://127.0.0.1:{provider.server_port}"
|
|
76
|
+
saved = (dict(connect.PROVIDERS), connect.CONFIG, serve.PORT, serve.CHANGES_PATH,
|
|
77
|
+
serve.endpoints.FILE, serve.endpoints.YAML, serve.endpoints.LLM_DIR, serve.DATA, serve.DISMISSED_PATH)
|
|
78
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
79
|
+
t = pathlib.Path(tmp)
|
|
80
|
+
try:
|
|
81
|
+
serve.PORT = dash.server_port
|
|
82
|
+
connect.CONFIG, serve.CHANGES_PATH = t / "connect.json", t / "changes.jsonl"
|
|
83
|
+
serve.endpoints.FILE, serve.endpoints.YAML, serve.endpoints.LLM_DIR = (
|
|
84
|
+
t / "endpoints.json", t / "models.yaml", t)
|
|
85
|
+
# An owner's endpoint with its own attribution headers, which a sign-in must keep.
|
|
86
|
+
serve.endpoints.FILE.write_text(json.dumps({
|
|
87
|
+
"hub": {"base": fake + "/v1", "key": "hub", "default": True},
|
|
88
|
+
"fk": {"base": fake + "/v1", "key": "fk", "headers": {"X-Title": "owner"}}}), encoding="utf-8")
|
|
89
|
+
base = {"base": fake + "/v1", "docs": "https://x"}
|
|
90
|
+
connect.PROVIDERS.update(
|
|
91
|
+
fk={**base, "label": "Key", "flow": "key", "authorize": fake + "/auth", "token": fake + "/keys",
|
|
92
|
+
"entry": {"headers": {"X-Title": "Benchwarmers"}}},
|
|
93
|
+
fo={**base, "label": "OAuth", "flow": "oauth", "authorize": fake + "/auth", "token": fake + "/token",
|
|
94
|
+
"client_id": "cid", "entry": {"api": "responses"}},
|
|
95
|
+
fd={**base, "label": "Device", "flow": "device", "device": fake + "/device",
|
|
96
|
+
"token": fake + "/token", "client_id": "cid", "register": "an app at the fake"},
|
|
97
|
+
fl={**base, "label": "Local", "flow": "local"},
|
|
98
|
+
fc={**base, "label": "Code", "flow": "oauth", "authorize": fake + "/auth", "token": fake + "/token",
|
|
99
|
+
"device": fake + "/device", "client_id": "cid"})
|
|
100
|
+
|
|
101
|
+
def callback(pid: str, endpoint: str = "") -> str:
|
|
102
|
+
r = serve.connect_start(pid, endpoint)
|
|
103
|
+
assert r["ok"], r
|
|
104
|
+
q = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(r["url"]).query))
|
|
105
|
+
back = q.get("callback_url") or q.get("redirect_uri")
|
|
106
|
+
assert f":{dash.server_port}/connect/callback" in back, q
|
|
107
|
+
challenges["code-" + pid] = q["code_challenge"]
|
|
108
|
+
# The key flow is sent back WITHOUT state, as OpenRouter may do:
|
|
109
|
+
# the id in the callback's path is what finds the sign-in.
|
|
110
|
+
url = (back + "?code=code-" + pid) if "callback_url" in q else \
|
|
111
|
+
back + "?" + urllib.parse.urlencode({"state": q["state"], "code": "code-" + pid})
|
|
112
|
+
url = url.replace("localhost", "127.0.0.1")
|
|
113
|
+
page = urllib.request.urlopen(url, timeout=10).read().decode()
|
|
114
|
+
assert serve.connect_poll(r["id"])["ok"], pid # the dashboard tab hears it
|
|
115
|
+
return page
|
|
116
|
+
|
|
117
|
+
# A provider can be several endpoints: "fk" is taken (the owner's, by
|
|
118
|
+
# key), so a sign-in adds "fk-2" and leaves "fk" exactly as it was.
|
|
119
|
+
before = serve.endpoints.load()["fk"]
|
|
120
|
+
page = callback("fk")
|
|
121
|
+
assert "Key is connected as fk-2" in page and "lists 2 models" in page, page
|
|
122
|
+
data, keys = serve.endpoints.load(), serve.endpoints.keys()
|
|
123
|
+
assert data["fk"] == before and "fk" not in keys, data["fk"]
|
|
124
|
+
assert data["fk-2"]["headers"] == {"X-Title": "Benchwarmers"}, data["fk-2"]
|
|
125
|
+
assert keys["fk-2"] == "sk-fake" and data["fk-2"]["connect"]["provider"] == "fk", (keys, data)
|
|
126
|
+
assert "sk-fake" not in page and "sk-fake" not in serve.CHANGES_PATH.read_text(encoding="utf-8")
|
|
127
|
+
# Signing that one in again keeps its name and its own headers.
|
|
128
|
+
connect.locked.update(serve.endpoints.FILE, lambda d: d["fk-2"].update(headers={"X-Title": "mine"}))
|
|
129
|
+
assert "connected as fk-2" in callback("fk", "fk-2")
|
|
130
|
+
assert serve.endpoints.load()["fk-2"]["headers"] == {"X-Title": "mine"}
|
|
131
|
+
assert not serve.connect_start("fk", "fk")["ok"] # never signed in: not its button
|
|
132
|
+
# Add refuses a name in use and offers a free one; Change key edits it.
|
|
133
|
+
refused = serve.save_endpoint("fk", fake + "/v1", "k", new=True)
|
|
134
|
+
assert not refused["ok"] and "fk-3" in refused["errors"][0], refused
|
|
135
|
+
# A sign-in can be given its own name instead of the next number...
|
|
136
|
+
assert "connected as work-or" in callback("fk", "work-or")
|
|
137
|
+
# ...but not one in use, one that is not a name, or a key the store already holds.
|
|
138
|
+
serve.endpoints.set_key("someones", "theirs")
|
|
139
|
+
for name in ("hub", "Bad Name", "someones"):
|
|
140
|
+
assert not serve.connect_start("fk", name)["ok"], name
|
|
141
|
+
assert serve.endpoints.keys()["someones"] == "theirs"
|
|
142
|
+
assert {p["id"]: p for p in serve.connect_list()["providers"]}["fk"]["suggest"] == "fk-3"
|
|
143
|
+
|
|
144
|
+
page = callback("fo")
|
|
145
|
+
data, keys = serve.endpoints.load(), serve.endpoints.keys()
|
|
146
|
+
assert keys["fo"] == "at" and keys["fo.refresh"] == "rt", keys
|
|
147
|
+
assert data["fo"]["connect"]["refreshes"] and data["fo"]["api"] == "responses", data["fo"]
|
|
148
|
+
serve.endpoints.YAML.write_text(serve.endpoints.yaml_block("fo-m1", "m1", "fo")
|
|
149
|
+
+ serve.endpoints.yaml_block("fk-m1", "m1", "fk"), encoding="utf-8")
|
|
150
|
+
assert client.backend("fo-m1") == "builtin", "a renewing endpoint must not go through llm"
|
|
151
|
+
assert client.backend("fk-m1") == client.backend() # a plain key: as configured
|
|
152
|
+
|
|
153
|
+
bad = urllib.request.urlopen(f"http://127.0.0.1:{dash.server_port}/connect/callback?state=nope&code=x",
|
|
154
|
+
timeout=10).read().decode()
|
|
155
|
+
assert "Not connected" in bad and "unknown or has expired" in bad, bad
|
|
156
|
+
|
|
157
|
+
d = serve.connect_start("fd")
|
|
158
|
+
assert d["ok"] and d["code"] == "WXYZ-0000", d
|
|
159
|
+
done = serve.connect_poll(d["id"])
|
|
160
|
+
assert done["ok"] and done["endpoint"] == "fd" and serve.endpoints.keys()["fd"] == "dev-token", done
|
|
161
|
+
|
|
162
|
+
local = serve.connect_start("fl")
|
|
163
|
+
assert local["ok"] and serve.endpoints.load()["fl"]["base"] == fake + "/v1", local
|
|
164
|
+
assert serve.connect_list_suggest("fl") == "fl" # adding it again refreshes it
|
|
165
|
+
|
|
166
|
+
# Use your own app: this install's client id instead of the shipped one, and back.
|
|
167
|
+
assert serve.connect_app("fd", "Iv1.own")["ok"]
|
|
168
|
+
row = {p["id"]: p for p in serve.connect_list()["providers"]}["fd"]
|
|
169
|
+
assert row["own_id"] == "Iv1.own" and row["shipped"] and connect.client_id("fd") == "Iv1.own", row
|
|
170
|
+
assert serve.connect_app("fd", "")["ok"] and connect.client_id("fd") == "cid"
|
|
171
|
+
assert not serve.connect_app("fk", "x-id")["ok"] # nothing to register for it
|
|
172
|
+
|
|
173
|
+
# A key typed over a sign-in replaces it, or the stale refresh would overwrite it.
|
|
174
|
+
assert serve.save_endpoint("fo", fake + "/v1", "pasted")["ok"]
|
|
175
|
+
assert "connect" not in serve.endpoints.load()["fo"] and serve.endpoints.keys()["fo"] == "pasted"
|
|
176
|
+
assert client.backend("fo-m1") == "builtin" # still the Responses API
|
|
177
|
+
assert not serve.connect_start("fo", "fo")["ok"] # no longer a sign-in to renew
|
|
178
|
+
callback("fo") # a new sign-in: its own endpoint
|
|
179
|
+
assert serve.endpoints.load()["fo-2"]["connect"]["provider"] == "fo"
|
|
180
|
+
|
|
181
|
+
listed = {p["id"]: p for p in serve.connect_list()["providers"]}
|
|
182
|
+
assert not listed["fk"]["missing"] and listed["fk"]["flow"] == "key", listed["fk"]
|
|
183
|
+
rows = {r["name"]: r for r in serve.endpoint_list()["endpoints"]}
|
|
184
|
+
assert rows["fo-2"]["connect"]["label"] == "OAuth" and rows["fo-2"]["connect"]["flow"] == "oauth"
|
|
185
|
+
assert not rows["fo-2"]["connect"]["expired"] and rows["hub"]["connect"] is None
|
|
186
|
+
|
|
187
|
+
# From a browser on another machine: by code where the provider has a device URL...
|
|
188
|
+
c = serve.connect_start("fc", "", True)
|
|
189
|
+
assert c["ok"] and c["code"] == "WXYZ-0000" and "url" not in c, c
|
|
190
|
+
assert serve.connect_poll(c["id"])["endpoint"] == "fc"
|
|
191
|
+
# ...and refused where it signs in only through a page on this machine.
|
|
192
|
+
assert not serve.connect_start("fk", "", True)["ok"]
|
|
193
|
+
assert {p["id"]: p for p in serve.connect_list()["providers"]}["fc"]["code"]
|
|
194
|
+
|
|
195
|
+
# Make default: moved, the default can't be removed, and records keyed by
|
|
196
|
+
# endpoint are renamed with it (bare on the default, served@endpoint elsewhere).
|
|
197
|
+
cfg = t / "config"
|
|
198
|
+
cfg.mkdir()
|
|
199
|
+
serve.DATA, serve.DISMISSED_PATH = t, cfg / "dismissed.json"
|
|
200
|
+
(cfg / "served-snapshot.json").write_text(json.dumps({"last_seen": {
|
|
201
|
+
"m1": "d", "m1@fk-2": "d", "a@b": "d"}, "first_missing": {"m9": "d"}}), encoding="utf-8")
|
|
202
|
+
serve.DISMISSED_PATH.write_text(json.dumps({"m2": {}, "m3@fk-2": {}}), encoding="utf-8")
|
|
203
|
+
assert serve.set_default("fk-2")["ok"] and serve.endpoints.default() == "fk-2"
|
|
204
|
+
snap = json.loads((cfg / "served-snapshot.json").read_text(encoding="utf-8"))
|
|
205
|
+
assert set(snap["last_seen"]) == {"m1@hub", "m1", "a@b@hub"}, snap # "@b" is not an endpoint
|
|
206
|
+
assert set(snap["first_missing"]) == {"m9@hub"}, snap
|
|
207
|
+
assert set(json.loads(serve.DISMISSED_PATH.read_text(encoding="utf-8"))) == {"m2@hub", "m3"}
|
|
208
|
+
assert not serve.remove_endpoint("fk-2")["ok"] and not serve.set_default("nope")["ok"]
|
|
209
|
+
assert sum(1 for e in serve.endpoints.load().values() if e.get("default")) == 1
|
|
210
|
+
finally:
|
|
211
|
+
connect.PROVIDERS.clear()
|
|
212
|
+
connect.PROVIDERS.update(saved[0])
|
|
213
|
+
(connect.CONFIG, serve.PORT, serve.CHANGES_PATH, serve.endpoints.FILE, serve.endpoints.YAML,
|
|
214
|
+
serve.endpoints.LLM_DIR, serve.DATA, serve.DISMISSED_PATH) = saved[1:]
|
|
215
|
+
provider.shutdown()
|
|
216
|
+
dash.shutdown()
|
|
217
|
+
print("ok")
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
sys.exit(main())
|