api-key-manager 2.1.0__py3-none-any.whl

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 (73) hide show
  1. api_key_manager-2.1.0.dist-info/METADATA +709 -0
  2. api_key_manager-2.1.0.dist-info/RECORD +73 -0
  3. api_key_manager-2.1.0.dist-info/WHEEL +5 -0
  4. api_key_manager-2.1.0.dist-info/entry_points.txt +2 -0
  5. api_key_manager-2.1.0.dist-info/top_level.txt +1 -0
  6. key_manager/__init__.py +16 -0
  7. key_manager/__main__.py +5 -0
  8. key_manager/api_models.py +358 -0
  9. key_manager/checker.py +51 -0
  10. key_manager/cli.py +270 -0
  11. key_manager/config.py +61 -0
  12. key_manager/core.py +205 -0
  13. key_manager/detector.py +335 -0
  14. key_manager/errors.py +179 -0
  15. key_manager/i18n.py +142 -0
  16. key_manager/logger.py +207 -0
  17. key_manager/model_capabilities.py +412 -0
  18. key_manager/parser.py +153 -0
  19. key_manager/providers/__init__.py +283 -0
  20. key_manager/providers/ai302.py +109 -0
  21. key_manager/providers/anthropic.py +109 -0
  22. key_manager/providers/baichuan.py +97 -0
  23. key_manager/providers/base.py +312 -0
  24. key_manager/providers/cerebras.py +109 -0
  25. key_manager/providers/cohere.py +90 -0
  26. key_manager/providers/cstcloud.py +122 -0
  27. key_manager/providers/dashscope.py +120 -0
  28. key_manager/providers/dashscope_coding.py +122 -0
  29. key_manager/providers/deepseek.py +166 -0
  30. key_manager/providers/dmxapi.py +109 -0
  31. key_manager/providers/doubao.py +109 -0
  32. key_manager/providers/fireworks.py +109 -0
  33. key_manager/providers/google.py +99 -0
  34. key_manager/providers/grok.py +109 -0
  35. key_manager/providers/groq.py +109 -0
  36. key_manager/providers/huggingface.py +54 -0
  37. key_manager/providers/hyperbolic.py +109 -0
  38. key_manager/providers/infini.py +135 -0
  39. key_manager/providers/infini_coding.py +124 -0
  40. key_manager/providers/kimi.py +121 -0
  41. key_manager/providers/kimi_coding.py +124 -0
  42. key_manager/providers/longcat.py +123 -0
  43. key_manager/providers/mimo.py +109 -0
  44. key_manager/providers/mimo_plan.py +140 -0
  45. key_manager/providers/minimax.py +97 -0
  46. key_manager/providers/minimax_plan.py +122 -0
  47. key_manager/providers/mistral.py +109 -0
  48. key_manager/providers/models_registry.py +2901 -0
  49. key_manager/providers/modelscope.py +134 -0
  50. key_manager/providers/nvidia.py +109 -0
  51. key_manager/providers/ocoolai.py +109 -0
  52. key_manager/providers/openai.py +140 -0
  53. key_manager/providers/openrouter.py +119 -0
  54. key_manager/providers/perplexity.py +109 -0
  55. key_manager/providers/poe.py +109 -0
  56. key_manager/providers/ppio.py +109 -0
  57. key_manager/providers/replicate.py +54 -0
  58. key_manager/providers/siliconflow.py +121 -0
  59. key_manager/providers/stepfun.py +132 -0
  60. key_manager/providers/tencent_hunyuan.py +122 -0
  61. key_manager/providers/together.py +134 -0
  62. key_manager/providers/yi.py +97 -0
  63. key_manager/providers/zai.py +109 -0
  64. key_manager/providers/zhipu.py +127 -0
  65. key_manager/providers/zhipu_coding.py +124 -0
  66. key_manager/proxy.py +70 -0
  67. key_manager/ssrf.py +68 -0
  68. key_manager/storage.py +134 -0
  69. key_manager/tester.py +137 -0
  70. key_manager/url_override.py +5 -0
  71. key_manager/validator.py +185 -0
  72. key_manager/web.py +1512 -0
  73. key_manager/webhook.py +257 -0
@@ -0,0 +1,109 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class DoubaoProvider(ProviderBase):
7
+ name = "doubao"
8
+ base_url = "https://ark.cn-beijing.volces.com/api/v3"
9
+ check_endpoint = "/models"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {"Authorization": f"Bearer {key}"}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ headers = self.build_headers(key)
16
+ try:
17
+ resp = await client.get(
18
+ f"{self.get_base_url()}{self.check_endpoint}",
19
+ headers=headers
20
+ )
21
+ if resp.status_code == 200:
22
+ data = resp.json()
23
+ if "data" in data:
24
+ return [m["id"] for m in data["data"] if "id" in m]
25
+ return []
26
+ except Exception:
27
+ return []
28
+
29
+ async def check(self, client, key: str) -> CheckResult:
30
+ """Real usage test - try to make a minimal chat completion request."""
31
+ headers = self.build_headers(key)
32
+ headers["Content-Type"] = "application/json"
33
+ start = time.monotonic()
34
+ try:
35
+ resp = await client.post(
36
+ f"{self.get_base_url()}/chat/completions",
37
+ headers=headers,
38
+ json={
39
+ "model": "doubao-pro-32k",
40
+ "messages": [{"role": "user", "content": "hi"}],
41
+ "max_tokens": 5
42
+ }
43
+ )
44
+ latency = (time.monotonic() - start) * 1000
45
+
46
+ if resp.status_code == 200:
47
+ return CheckResult(True, 200, latency, None)
48
+ elif resp.status_code in (401, 403):
49
+ return CheckResult(False, resp.status_code, latency, "invalid key or forbidden")
50
+ elif resp.status_code == 429:
51
+ return CheckResult(False, 429, latency, "rate limited")
52
+ else:
53
+ try:
54
+ data = resp.json()
55
+ error_msg = data.get("error", {}).get("message", f"status {resp.status_code}")
56
+ except:
57
+ error_msg = f"status {resp.status_code}"
58
+ return CheckResult(False, resp.status_code, latency, error_msg)
59
+ except Exception as e:
60
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
61
+
62
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
63
+ headers = self.build_headers(key)
64
+ last_success = None
65
+ for step in token_steps:
66
+ try:
67
+ resp = await client.post(
68
+ f"{self.get_base_url()}/chat/completions",
69
+ headers=headers,
70
+ json={
71
+ "model": "doubao-pro-32k",
72
+ "messages": [{"role": "user", "content": "hi"}],
73
+ "max_tokens": step
74
+ }
75
+ )
76
+ if resp.status_code == 200:
77
+ last_success = step
78
+ elif resp.status_code in (400, 413):
79
+ break
80
+ elif resp.status_code == 429:
81
+ await asyncio.sleep(1)
82
+ continue
83
+ else:
84
+ break
85
+ except Exception:
86
+ break
87
+ return TestResult(max_tokens=last_success)
88
+
89
+
90
+ async def check_real(self, client, key: str) -> CheckResult:
91
+ return await self.check(client, key)
92
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
93
+ headers = self.build_headers(key)
94
+ last_success = None
95
+ for step in concurrency_steps:
96
+ tasks = [self._probe(client, headers) for _ in range(step)]
97
+ results = await asyncio.gather(*tasks, return_exceptions=True)
98
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
99
+ if rate_limited / step >= 0.3:
100
+ break
101
+ last_success = step
102
+ return TestResult(max_concurrency=last_success)
103
+
104
+ async def _probe(self, client, headers) -> bool:
105
+ try:
106
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
107
+ return resp.status_code == 200
108
+ except Exception:
109
+ return False
@@ -0,0 +1,109 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class FireworksProvider(ProviderBase):
7
+ name = "fireworks"
8
+ base_url = "https://api.fireworks.ai"
9
+ check_endpoint = "/inference/v1/models"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {"Authorization": f"Bearer {key}"}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ headers = self.build_headers(key)
16
+ try:
17
+ resp = await client.get(
18
+ f"{self.get_base_url()}{self.check_endpoint}",
19
+ headers=headers
20
+ )
21
+ if resp.status_code == 200:
22
+ data = resp.json()
23
+ if "data" in data:
24
+ return [m["id"] for m in data["data"] if "id" in m]
25
+ return []
26
+ except Exception:
27
+ return []
28
+
29
+ async def check(self, client, key: str) -> CheckResult:
30
+ """Real usage test - try to make a minimal chat completion request."""
31
+ headers = self.build_headers(key)
32
+ headers["Content-Type"] = "application/json"
33
+ start = time.monotonic()
34
+ try:
35
+ resp = await client.post(
36
+ f"{self.get_base_url()}/inference/v1/chat/completions",
37
+ headers=headers,
38
+ json={
39
+ "model": "accounts/fireworks/models/llama-v3p1-8b-instruct",
40
+ "messages": [{"role": "user", "content": "hi"}],
41
+ "max_tokens": 5
42
+ }
43
+ )
44
+ latency = (time.monotonic() - start) * 1000
45
+
46
+ if resp.status_code == 200:
47
+ return CheckResult(True, 200, latency, None)
48
+ elif resp.status_code in (401, 403):
49
+ return CheckResult(False, resp.status_code, latency, "invalid key or forbidden")
50
+ elif resp.status_code == 429:
51
+ return CheckResult(False, 429, latency, "rate limited")
52
+ else:
53
+ try:
54
+ data = resp.json()
55
+ error_msg = data.get("error", {}).get("message", f"status {resp.status_code}")
56
+ except:
57
+ error_msg = f"status {resp.status_code}"
58
+ return CheckResult(False, resp.status_code, latency, error_msg)
59
+ except Exception as e:
60
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
61
+
62
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
63
+ headers = self.build_headers(key)
64
+ last_success = None
65
+ for step in token_steps:
66
+ try:
67
+ resp = await client.post(
68
+ f"{self.get_base_url()}/inference/v1/chat/completions",
69
+ headers=headers,
70
+ json={
71
+ "model": "accounts/fireworks/models/llama-v3p1-8b-instruct",
72
+ "messages": [{"role": "user", "content": "hi"}],
73
+ "max_tokens": step
74
+ }
75
+ )
76
+ if resp.status_code == 200:
77
+ last_success = step
78
+ elif resp.status_code in (400, 413):
79
+ break
80
+ elif resp.status_code == 429:
81
+ await asyncio.sleep(1)
82
+ continue
83
+ else:
84
+ break
85
+ except Exception:
86
+ break
87
+ return TestResult(max_tokens=last_success)
88
+
89
+
90
+ async def check_real(self, client, key: str) -> CheckResult:
91
+ return await self.check(client, key)
92
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
93
+ headers = self.build_headers(key)
94
+ last_success = None
95
+ for step in concurrency_steps:
96
+ tasks = [self._probe(client, headers) for _ in range(step)]
97
+ results = await asyncio.gather(*tasks, return_exceptions=True)
98
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
99
+ if rate_limited / step >= 0.3:
100
+ break
101
+ last_success = step
102
+ return TestResult(max_concurrency=last_success)
103
+
104
+ async def _probe(self, client, headers) -> bool:
105
+ try:
106
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
107
+ return resp.status_code == 200
108
+ except Exception:
109
+ return False
@@ -0,0 +1,99 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class GoogleProvider(ProviderBase):
7
+ name = "google"
8
+ base_url = "https://generativelanguage.googleapis.com"
9
+ check_endpoint = "/v1beta/models"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ try:
16
+ resp = await client.get(
17
+ f"{self.get_base_url()}{self.check_endpoint}?key={key}"
18
+ )
19
+ if resp.status_code == 200:
20
+ data = resp.json()
21
+ if "models" in data:
22
+ return [m["name"].split("/")[-1] for m in data["models"] if "name" in m]
23
+ return []
24
+ except Exception:
25
+ return []
26
+
27
+ async def check(self, client, key: str) -> CheckResult:
28
+ """Real usage test - try to make a minimal chat completion request."""
29
+ start = time.monotonic()
30
+ try:
31
+ resp = await client.post(
32
+ f"{self.get_base_url()}/v1beta/models/gemini-1.5-flash:generateContent?key={key}",
33
+ json={
34
+ "contents": [{"parts": [{"text": "hi"}]}],
35
+ "generationConfig": {"maxOutputTokens": 5}
36
+ }
37
+ )
38
+ latency = (time.monotonic() - start) * 1000
39
+
40
+ if resp.status_code == 200:
41
+ return CheckResult(True, 200, latency, None)
42
+ elif resp.status_code in (400, 403):
43
+ return CheckResult(False, resp.status_code, latency, "invalid key or forbidden")
44
+ elif resp.status_code == 429:
45
+ return CheckResult(False, 429, latency, "rate limited")
46
+ else:
47
+ try:
48
+ data = resp.json()
49
+ error_msg = data.get("error", {}).get("message", f"status {resp.status_code}")
50
+ except:
51
+ error_msg = f"status {resp.status_code}"
52
+ return CheckResult(False, resp.status_code, latency, error_msg)
53
+ except Exception as e:
54
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
55
+
56
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
57
+ last_success = None
58
+ for step in token_steps:
59
+ try:
60
+ resp = await client.post(
61
+ f"{self.get_base_url()}/v1beta/models/gemini-1.5-flash:generateContent?key={key}",
62
+ json={
63
+ "contents": [{"parts": [{"text": "hi"}]}],
64
+ "generationConfig": {"maxOutputTokens": step}
65
+ }
66
+ )
67
+ if resp.status_code == 200:
68
+ last_success = step
69
+ elif resp.status_code in (400, 413):
70
+ break
71
+ elif resp.status_code == 429:
72
+ await asyncio.sleep(1)
73
+ continue
74
+ else:
75
+ break
76
+ except Exception:
77
+ break
78
+ return TestResult(max_tokens=last_success)
79
+
80
+
81
+ async def check_real(self, client, key: str) -> CheckResult:
82
+ return await self.check(client, key)
83
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
84
+ last_success = None
85
+ for step in concurrency_steps:
86
+ tasks = [self._probe(client, key) for _ in range(step)]
87
+ results = await asyncio.gather(*tasks, return_exceptions=True)
88
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
89
+ if rate_limited / step >= 0.3:
90
+ break
91
+ last_success = step
92
+ return TestResult(max_concurrency=last_success)
93
+
94
+ async def _probe(self, client, key) -> bool:
95
+ try:
96
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}?key={key}")
97
+ return resp.status_code == 200
98
+ except Exception:
99
+ return False
@@ -0,0 +1,109 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class GrokProvider(ProviderBase):
7
+ name = "grok"
8
+ base_url = "https://api.x.ai"
9
+ check_endpoint = "/v1/models"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {"Authorization": f"Bearer {key}"}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ headers = self.build_headers(key)
16
+ try:
17
+ resp = await client.get(
18
+ f"{self.get_base_url()}{self.check_endpoint}",
19
+ headers=headers
20
+ )
21
+ if resp.status_code == 200:
22
+ data = resp.json()
23
+ if "data" in data:
24
+ return [m["id"] for m in data["data"] if "id" in m]
25
+ return []
26
+ except Exception:
27
+ return []
28
+
29
+ async def check(self, client, key: str) -> CheckResult:
30
+ """Real usage test - try to make a minimal chat completion request."""
31
+ headers = self.build_headers(key)
32
+ headers["Content-Type"] = "application/json"
33
+ start = time.monotonic()
34
+ try:
35
+ resp = await client.post(
36
+ f"{self.get_base_url()}/v1/chat/completions",
37
+ headers=headers,
38
+ json={
39
+ "model": "grok-1",
40
+ "messages": [{"role": "user", "content": "hi"}],
41
+ "max_tokens": 5
42
+ }
43
+ )
44
+ latency = (time.monotonic() - start) * 1000
45
+
46
+ if resp.status_code == 200:
47
+ return CheckResult(True, 200, latency, None)
48
+ elif resp.status_code in (401, 403):
49
+ return CheckResult(False, resp.status_code, latency, "invalid key or forbidden")
50
+ elif resp.status_code == 429:
51
+ return CheckResult(False, 429, latency, "rate limited")
52
+ else:
53
+ try:
54
+ data = resp.json()
55
+ error_msg = data.get("error", {}).get("message", f"status {resp.status_code}")
56
+ except:
57
+ error_msg = f"status {resp.status_code}"
58
+ return CheckResult(False, resp.status_code, latency, error_msg)
59
+ except Exception as e:
60
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
61
+
62
+
63
+ async def check_real(self, client, key: str) -> CheckResult:
64
+ return await self.check(client, key)
65
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
66
+ headers = self.build_headers(key)
67
+ last_success = None
68
+ for step in token_steps:
69
+ try:
70
+ resp = await client.post(
71
+ f"{self.get_base_url()}/v1/chat/completions",
72
+ headers=headers,
73
+ json={
74
+ "model": "grok-2-1212",
75
+ "messages": [{"role": "user", "content": "hi"}],
76
+ "max_tokens": step
77
+ }
78
+ )
79
+ if resp.status_code == 200:
80
+ last_success = step
81
+ elif resp.status_code in (400, 413):
82
+ break
83
+ elif resp.status_code == 429:
84
+ await asyncio.sleep(1)
85
+ continue
86
+ else:
87
+ break
88
+ except Exception:
89
+ break
90
+ return TestResult(max_tokens=last_success)
91
+
92
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
93
+ headers = self.build_headers(key)
94
+ last_success = None
95
+ for step in concurrency_steps:
96
+ tasks = [self._probe(client, headers) for _ in range(step)]
97
+ results = await asyncio.gather(*tasks, return_exceptions=True)
98
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
99
+ if rate_limited / step >= 0.3:
100
+ break
101
+ last_success = step
102
+ return TestResult(max_concurrency=last_success)
103
+
104
+ async def _probe(self, client, headers) -> bool:
105
+ try:
106
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
107
+ return resp.status_code == 200
108
+ except Exception:
109
+ return False
@@ -0,0 +1,109 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class GroqProvider(ProviderBase):
7
+ name = "groq"
8
+ base_url = "https://api.groq.com"
9
+ check_endpoint = "/openai/v1/models"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {"Authorization": f"Bearer {key}"}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ headers = self.build_headers(key)
16
+ try:
17
+ resp = await client.get(
18
+ f"{self.get_base_url()}{self.check_endpoint}",
19
+ headers=headers
20
+ )
21
+ if resp.status_code == 200:
22
+ data = resp.json()
23
+ if "data" in data:
24
+ return [m["id"] for m in data["data"] if "id" in m]
25
+ return []
26
+ except Exception:
27
+ return []
28
+
29
+ async def check(self, client, key: str) -> CheckResult:
30
+ """Real usage test - try to make a minimal chat completion request."""
31
+ headers = self.build_headers(key)
32
+ headers["Content-Type"] = "application/json"
33
+ start = time.monotonic()
34
+ try:
35
+ resp = await client.post(
36
+ f"{self.get_base_url()}/openai/v1/chat/completions",
37
+ headers=headers,
38
+ json={
39
+ "model": "llama3-8b-8192",
40
+ "messages": [{"role": "user", "content": "hi"}],
41
+ "max_tokens": 5
42
+ }
43
+ )
44
+ latency = (time.monotonic() - start) * 1000
45
+
46
+ if resp.status_code == 200:
47
+ return CheckResult(True, 200, latency, None)
48
+ elif resp.status_code in (401, 403):
49
+ return CheckResult(False, resp.status_code, latency, "invalid key or forbidden")
50
+ elif resp.status_code == 429:
51
+ return CheckResult(False, 429, latency, "rate limited")
52
+ else:
53
+ try:
54
+ data = resp.json()
55
+ error_msg = data.get("error", {}).get("message", f"status {resp.status_code}")
56
+ except:
57
+ error_msg = f"status {resp.status_code}"
58
+ return CheckResult(False, resp.status_code, latency, error_msg)
59
+ except Exception as e:
60
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
61
+
62
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
63
+ headers = self.build_headers(key)
64
+ last_success = None
65
+ for step in token_steps:
66
+ try:
67
+ resp = await client.post(
68
+ f"{self.get_base_url()}/openai/v1/chat/completions",
69
+ headers=headers,
70
+ json={
71
+ "model": "llama3-8b-8192",
72
+ "messages": [{"role": "user", "content": "hi"}],
73
+ "max_tokens": step
74
+ }
75
+ )
76
+ if resp.status_code == 200:
77
+ last_success = step
78
+ elif resp.status_code in (400, 413):
79
+ break
80
+ elif resp.status_code == 429:
81
+ await asyncio.sleep(1)
82
+ continue
83
+ else:
84
+ break
85
+ except Exception:
86
+ break
87
+ return TestResult(max_tokens=last_success)
88
+
89
+
90
+ async def check_real(self, client, key: str) -> CheckResult:
91
+ return await self.check(client, key)
92
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
93
+ headers = self.build_headers(key)
94
+ last_success = None
95
+ for step in concurrency_steps:
96
+ tasks = [self._probe(client, headers) for _ in range(step)]
97
+ results = await asyncio.gather(*tasks, return_exceptions=True)
98
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
99
+ if rate_limited / step >= 0.3:
100
+ break
101
+ last_success = step
102
+ return TestResult(max_concurrency=last_success)
103
+
104
+ async def _probe(self, client, headers) -> bool:
105
+ try:
106
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
107
+ return resp.status_code == 200
108
+ except Exception:
109
+ return False
@@ -0,0 +1,54 @@
1
+ import asyncio
2
+ import time
3
+ from .base import ProviderBase, CheckResult, TestResult
4
+
5
+
6
+ class HuggingFaceProvider(ProviderBase):
7
+ name = "huggingface"
8
+ base_url = "https://huggingface.co"
9
+ check_endpoint = "/api/whoami-v2"
10
+
11
+ def build_headers(self, key: str) -> dict:
12
+ return {"Authorization": f"Bearer {key}"}
13
+
14
+ async def get_models(self, client, key: str) -> list[str]:
15
+ return []
16
+
17
+ async def check(self, client, key: str) -> CheckResult:
18
+ headers = self.build_headers(key)
19
+ start = time.monotonic()
20
+ try:
21
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
22
+ latency = (time.monotonic() - start) * 1000
23
+ if resp.status_code == 200:
24
+ return CheckResult(True, 200, latency, None)
25
+ elif resp.status_code in (401, 403):
26
+ return CheckResult(False, resp.status_code, latency, "invalid key")
27
+ elif resp.status_code == 429:
28
+ return CheckResult(True, 429, latency, "rate limited")
29
+ else:
30
+ return CheckResult(False, resp.status_code, latency, f"status {resp.status_code}")
31
+ except Exception as e:
32
+ return CheckResult(False, None, (time.monotonic() - start) * 1000, str(e))
33
+
34
+ async def test_token_limit(self, client, key: str, token_steps: list[int]) -> TestResult:
35
+ return TestResult(max_tokens=None)
36
+
37
+ async def test_concurrency(self, client, key: str, concurrency_steps: list[int]) -> TestResult:
38
+ headers = self.build_headers(key)
39
+ last_success = None
40
+ for step in concurrency_steps:
41
+ tasks = [self._probe(client, headers) for _ in range(step)]
42
+ results = await asyncio.gather(*tasks, return_exceptions=True)
43
+ rate_limited = sum(1 for r in results if not isinstance(r, Exception) and not r)
44
+ if rate_limited / step >= 0.3:
45
+ break
46
+ last_success = step
47
+ return TestResult(max_concurrency=last_success)
48
+
49
+ async def _probe(self, client, headers) -> bool:
50
+ try:
51
+ resp = await client.get(f"{self.get_base_url()}{self.check_endpoint}", headers=headers)
52
+ return resp.status_code == 200
53
+ except Exception:
54
+ return False