@wendellresearch-ai/benchwarmers 0.2.2 → 0.3.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.
@@ -88,6 +88,18 @@ HUBS = {"openwebui": "Open WebUI", "litellm": "LiteLLM", "cliproxy": "CLI Proxy
88
88
  # endpoint(), plan(connection, model) and add(connection, model), and the two
89
89
  # whose connections only the hub knows also upstreams(), for Discover.
90
90
  ADAPTERS = {"openwebui": "owui", "litellm": "litellm_hub", "cliproxy": "cliproxy"}
91
+ # `api`: what an endpoint speaks, chat completions unless it says otherwise.
92
+ # The built-in client builds the others' native requests (metrics/formats.py);
93
+ # `responses` is set by Connect for ChatGPT's plan, the native two on the Add
94
+ # form, for an API key whose provider has no chat completions route.
95
+ APIS = {"chat": "OpenAI chat completions", "responses": "OpenAI Responses",
96
+ "anthropic": "Anthropic Messages (native)", "gemini": "Gemini generateContent (native)"}
97
+
98
+
99
+ def auth_headers(api: str, secret: str) -> dict:
100
+ """How a key is sent: Bearer, except to Anthropic's and Gemini's native APIs."""
101
+ return {"anthropic": {"x-api-key": secret}, "gemini": {"x-goog-api-key": secret}}.get(
102
+ api, {"Authorization": f"Bearer {secret}"})
91
103
 
92
104
 
93
105
  def adapter(which: str):
@@ -271,13 +283,24 @@ def list_models(entry: dict, secret: str = "", timeout: int = 30) -> dict[str, d
271
283
  before saving it). `models_url` when the list lives elsewhere (GitHub
272
284
  Models: its catalog, a bare JSON list); entries marked `visibility` other
273
285
  than "list" are left out (ChatGPT's plan lists ones it will not serve)."""
286
+ api = entry.get("api") or "chat"
274
287
  headers = dict(entry.get("headers") or {})
275
288
  if secret:
276
- headers["Authorization"] = f"Bearer {secret}"
289
+ headers.update(auth_headers(api, secret))
290
+ if api == "anthropic":
291
+ headers["anthropic-version"] = "2023-06-01"
277
292
  url = entry.get("models_url") or entry["base"].rstrip("/") + "/models"
293
+ if api == "gemini" and not entry.get("models_url"):
294
+ url += "?pageSize=1000" # 50 a page otherwise
278
295
  with urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=timeout) as response:
279
296
  data = json.load(response)
280
- rows = data.get("data", []) if isinstance(data, dict) else data
297
+ if api == "gemini" and isinstance(data, dict):
298
+ # Gemini's list: {"models": [{"name": "models/gemini-x", ...}]}; only
299
+ # what generateContent serves, by its bare name.
300
+ rows = [{**m, "id": str(m.get("name", "")).removeprefix("models/")} for m in data.get("models", [])
301
+ if isinstance(m, dict) and "generateContent" in (m.get("supportedGenerationMethods") or ())]
302
+ else:
303
+ rows = data.get("data", []) if isinstance(data, dict) else data
281
304
  return {e["id"]: e for e in rows
282
305
  if isinstance(e, dict) and e.get("id") and e.get("visibility", "list") == "list"}
283
306