@wendellresearch-ai/benchwarmers 0.2.0 → 0.2.2

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.
Files changed (103) hide show
  1. package/AGENTS.md +152 -147
  2. package/BENCHMARKS.md +262 -262
  3. package/GATEWAY.md +415 -272
  4. package/NOTICE +28 -28
  5. package/PRIVACY.md +46 -46
  6. package/README.md +408 -314
  7. package/SECURITY.md +83 -83
  8. package/TERMS.md +22 -22
  9. package/USAGE.md +186 -163
  10. package/VERSION +1 -1
  11. package/bin/benchwarmers.js +16 -74
  12. package/cli.py +674 -0
  13. package/examples/config/gateway.json +5 -5
  14. package/examples/config/model-roles.json +11 -11
  15. package/gateway.py +939 -755
  16. package/launcher.py +120 -0
  17. package/mcp_server.py +266 -266
  18. package/metrics/alerts.py +116 -116
  19. package/metrics/aliases.py +41 -41
  20. package/metrics/bench_collections.py +141 -141
  21. package/metrics/catalog.py +304 -295
  22. package/metrics/client.py +804 -394
  23. package/metrics/cliproxy.py +191 -191
  24. package/metrics/connect.py +691 -691
  25. package/metrics/cost.py +171 -152
  26. package/metrics/decide.py +227 -227
  27. package/metrics/discover.py +318 -318
  28. package/metrics/endpoints.py +436 -436
  29. package/metrics/formats.py +1021 -0
  30. package/metrics/guard.py +647 -592
  31. package/metrics/learn.py +234 -221
  32. package/metrics/ledger.py +148 -0
  33. package/metrics/litellm_hub.py +206 -206
  34. package/metrics/model_roles.py +94 -84
  35. package/metrics/offload.py +166 -166
  36. package/metrics/owui.py +205 -205
  37. package/metrics/pacing.py +248 -0
  38. package/metrics/paths.py +60 -60
  39. package/metrics/quota.py +383 -0
  40. package/metrics/route.py +581 -579
  41. package/metrics/scheduler.py +275 -275
  42. package/metrics/sources.py +167 -167
  43. package/metrics/wrong.py +144 -144
  44. package/package.json +69 -65
  45. package/perf/README.md +89 -89
  46. package/perf/run.py +284 -261
  47. package/scripts/auto_retire.py +367 -367
  48. package/scripts/check.py +113 -106
  49. package/scripts/check_deprecations.py +378 -378
  50. package/scripts/check_themes.py +103 -103
  51. package/scripts/init.py +124 -124
  52. package/scripts/list_served.py +78 -78
  53. package/scripts/notice.py +78 -78
  54. package/scripts/onboard.py +410 -331
  55. package/scripts/provider_tags.py +129 -129
  56. package/scripts/reprefix.py +136 -136
  57. package/scripts/sync_config.py +105 -105
  58. package/scripts/test_auth.py +255 -255
  59. package/scripts/test_cli.py +189 -0
  60. package/scripts/test_client.py +277 -233
  61. package/scripts/test_connect.py +222 -222
  62. package/scripts/test_first_run.py +180 -180
  63. package/scripts/test_formats.py +549 -0
  64. package/scripts/test_hubs.py +328 -328
  65. package/scripts/test_launcher.py +67 -0
  66. package/scripts/test_llm_log.py +85 -81
  67. package/scripts/test_mcp.py +178 -178
  68. package/scripts/test_merged.py +215 -214
  69. package/scripts/test_no_owui.py +291 -291
  70. package/scripts/test_owui.py +90 -90
  71. package/scripts/test_quota.py +334 -0
  72. package/scripts/test_serve_edit.py +453 -438
  73. package/scripts/test_server_json.py +79 -0
  74. package/scripts/test_verdict.py +102 -98
  75. package/scripts/test_vision.py +231 -228
  76. package/serve.py +3972 -3764
  77. package/suite/mine.py +154 -154
  78. package/suite/pace.py +145 -145
  79. package/suite/run.py +1046 -1042
  80. package/suite/seed.py +421 -421
  81. package/suite/tasks/claim-alias-unbound/fixtures/onboard.py +286 -286
  82. package/suite/tasks/claim-error-text-string/fixtures/decide.py +187 -187
  83. package/suite/tasks/claim-list-body/fixtures/gateway.py +235 -235
  84. package/suite/tasks/claim-member-nominated-twice/fixtures/gateway.py +284 -284
  85. package/suite/tasks/claim-nominee-uses-slot/fixtures/gateway.py +284 -284
  86. package/suite/tasks/claim-silent-exit/fixtures/gateway.py +235 -235
  87. package/suite/tasks/claim-sse-hang/fixtures/gateway.py +235 -235
  88. package/suite/tasks/claim-unknown-nominee-crash/fixtures/gateway.py +284 -284
  89. package/suite/tasks/rebench-matchms-809-bug/task.json +23 -23
  90. package/suite/tasks/rebench-matchms-809-control/task.json +27 -27
  91. package/suite/tasks/rebench-more-itertools-1028-bug/task.json +23 -23
  92. package/suite/tasks/rebench-more-itertools-1028-control/task.json +27 -27
  93. package/suite/tasks/rebench-narwhals-2864-bug/task.json +23 -23
  94. package/suite/tasks/rebench-narwhals-2864-control/task.json +27 -27
  95. package/suite/tasks/rebench-quantecon-py-769-bug/task.json +23 -23
  96. package/suite/tasks/rebench-quantecon-py-769-control/task.json +27 -27
  97. package/suite/tasks/rebench-scrapy-6867-bug/task.json +23 -23
  98. package/suite/tasks/rebench-scrapy-6867-control/task.json +27 -27
  99. package/ui/bench.css +430 -425
  100. package/ui/bench.js +1757 -1576
  101. package/ui/themes/dark.css +35 -35
  102. package/ui/themes/hardwood.css +52 -52
  103. package/ui/themes/rainforest.css +32 -32
@@ -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())