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