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,73 @@
1
+ key_manager/__init__.py,sha256=x4LlmQ4xZJVNwK-01OQdwdwc2H5PYz4VRLJ51gf3V4I,421
2
+ key_manager/__main__.py,sha256=GzkP1E6JO-GmOaq3z30Op1DQMS0i7YopewS5k7F2iAE,124
3
+ key_manager/api_models.py,sha256=EHMyl7NUefPihEEUBQWcG7bhKoalitq-cPn0jNlUNIQ,15946
4
+ key_manager/checker.py,sha256=srQUMT_StEDphSTYr3rYyKZPI2g4Je22RidzV5oz5DE,1675
5
+ key_manager/cli.py,sha256=KRouKOkRaMHhlk4d27Qv1IsgEsY8fYSqxQotM9UqXX4,9387
6
+ key_manager/config.py,sha256=bAKPmpdQXOwcKtfSqFV4-Vu3h83a1dQq0KQDB_I4Vvk,1814
7
+ key_manager/core.py,sha256=IoU7Ic4OnTaI4Dg6tZ0ILRmC5KgoZgloGubHc7izVzE,6419
8
+ key_manager/detector.py,sha256=8vHZkMMrmy_acGLceN4lqUt4b9nQfktr65LlF2EqRkk,13809
9
+ key_manager/errors.py,sha256=cB_z8qoYXvj3hlh86SCAjgNJBejR6w6YRdcmHNG-5hE,6088
10
+ key_manager/i18n.py,sha256=hqqFWanD6ybs8or5qg9FcOg9a7T61zLVdGVkUYRhevQ,4077
11
+ key_manager/logger.py,sha256=rzISVc4SBSJ06BV0houobmLpDYAqcTwQLXh7kpxjSRM,7922
12
+ key_manager/model_capabilities.py,sha256=o_Qb7gl9fB5HOxsGE2zRRq7Sj9Dpmnm-qazPezkgXtc,12758
13
+ key_manager/parser.py,sha256=oanTRB0h8hFLfn6oY-gU418KBcU5lT7A1ALrwERBwnQ,5429
14
+ key_manager/proxy.py,sha256=jrpm8-XnUxGhqJrQQh4Kh0SllpFB9RgdyXh927BYjwo,1894
15
+ key_manager/ssrf.py,sha256=xh4RexLgIudjWZH-2-Uroj_4V3yU5WkiNzdWb26oDcA,1992
16
+ key_manager/storage.py,sha256=qrwKKxbc-5684mNGStM11HjVU1YYlZdUvjsllFbYTic,4514
17
+ key_manager/tester.py,sha256=_jMZSmH9U5tPtFnd8ZZTEf0lLpWam2-UfjP2CYmn-lY,5107
18
+ key_manager/url_override.py,sha256=EQYre1FDcPd1oC9PLxeUoSiLwQ_vn--96Xhy_3LLiE0,252
19
+ key_manager/validator.py,sha256=zNHomlXNL6y8ro9OTMVPvk_sOdlF2Xn_Z84OWPVBENQ,7291
20
+ key_manager/web.py,sha256=trMpdtb383j70Z0_yTAWQ_r65akl7-Jr7JaohvE82SA,66778
21
+ key_manager/webhook.py,sha256=ke4-7kNyaDu6ZQqg_YeWeHPVe2kTM6ansQ4im93goYE,8055
22
+ key_manager/providers/__init__.py,sha256=UEPyPh4FN5nSijsNSxzENMDdwu0-GsHR4Pgk5MVy2AE,13968
23
+ key_manager/providers/ai302.py,sha256=-Rw6eC6wxwjVdze4LFs_iTq-7RlP84uEOVa7EOq8ccA,4421
24
+ key_manager/providers/anthropic.py,sha256=wUG9bwRAFFyWmmfoQhgvimyZWA5HdNUYcfcHSnQus_g,4382
25
+ key_manager/providers/baichuan.py,sha256=mzjSseNslrI1O9iRASRRlPEr5GQn8WGDKdVxo6O7sXM,4156
26
+ key_manager/providers/base.py,sha256=zmwPMoQk3-4o8s2N83HnH-KX836fFSzL24K9yJpQ_js,11819
27
+ key_manager/providers/cerebras.py,sha256=cbqTpKr6siDGHl_OovSP_5G6adpNj-rCJYUeU4C7dlA,4432
28
+ key_manager/providers/cohere.py,sha256=ccNRzCLdIumqIDPkPbml1tfvc02dsg-1us0Y1Iom3b0,3645
29
+ key_manager/providers/cstcloud.py,sha256=UwcNrltUl1p8pSALHwFB-D_-8vqgq6Rh5CdzmMF0Z9s,5346
30
+ key_manager/providers/dashscope.py,sha256=6Gf3slaXXLI1Pw0R-ocF2iGWWOeOEyvx9n4yPS6WhtI,5206
31
+ key_manager/providers/dashscope_coding.py,sha256=bIEt9-O4ZoDwZbp5geAkbnBv0CVGD1ksmT7zgl8QpUc,5404
32
+ key_manager/providers/deepseek.py,sha256=RHrk0pOPAxZP1JA8DXjGP5tFu-S39wyJxkDGIyp_W_Q,7074
33
+ key_manager/providers/dmxapi.py,sha256=uPUG6-dyWFRAHhin80PX7q1yczBHExiTD6E4KKbC-1Y,4426
34
+ key_manager/providers/doubao.py,sha256=h3l2h_6MvtTBrd79qBAaGxlfrfUPKme-IWwQKYjYRUc,4448
35
+ key_manager/providers/fireworks.py,sha256=ciecI3plQGsUL582YoIwGSCWmhV2Q6W-d9UiGRVNTO4,4545
36
+ key_manager/providers/google.py,sha256=Td1OhcIRJFPJEQ4VaUVTOBJUCDkBa4Bj1-0QdwqMFUs,4132
37
+ key_manager/providers/grok.py,sha256=R-zSjaLBzmaIg_E22_57jR555g_IHlMRIaY228K1bPc,4418
38
+ key_manager/providers/groq.py,sha256=Bht4XUApmUA3JZjuDnh_IUZp6p_PfBoblCrXdAi1uAE,4454
39
+ key_manager/providers/huggingface.py,sha256=1IjsoQNh3cdtf38u5Bh3mZQNFeHN069AFr7lFfExhrs,2352
40
+ key_manager/providers/hyperbolic.py,sha256=i13BASJn2jBCEFbaji3GPCovTZSJNJag6wUh9C9VLKg,4481
41
+ key_manager/providers/infini.py,sha256=PmrazMa7YcCEwXyjg5f4jqPrsLl56jtQ4MprmQ3Knh8,5597
42
+ key_manager/providers/infini_coding.py,sha256=yLCzH8CI5u5w4hyciGqgTn0aBJLrBgddIprBYnHEOLw,5408
43
+ key_manager/providers/kimi.py,sha256=T1M9k1cC-v0QGdIIYP4ddRhkkgpBwXThi_Ws6W9FyWw,5201
44
+ key_manager/providers/kimi_coding.py,sha256=THjpljhqjZirkGz8GOXxwq0GbFNAwXgobO3ax2gkUPI,5360
45
+ key_manager/providers/longcat.py,sha256=1EzQZ0FehXWA8IO7VZmNZpU4B10je9Uy4EaegHnj7_8,5035
46
+ key_manager/providers/mimo.py,sha256=OxSJu9-E0ZkniyIpv6AHZ43_gH733lQinyncYvMZ5o4,4419
47
+ key_manager/providers/mimo_plan.py,sha256=vdRwqOC9vK0FDKKM5goyIYYLrHVwYzf2xV8NWbFE9vk,5904
48
+ key_manager/providers/minimax.py,sha256=WrxtjpLOg3vE9ZYTl6enPvkOKKoEZFoJlj4z96Mo4vg,4142
49
+ key_manager/providers/minimax_plan.py,sha256=FOTLFENsKaoBf7XRRs3DOxTh7nqCqP8MHPkcOHpSowY,5407
50
+ key_manager/providers/mistral.py,sha256=loySnKHvxz4j-ZWfCyWCT-RtwtCJiHAtr41oR4jmiTQ,4437
51
+ key_manager/providers/models_registry.py,sha256=XSegzg6L-pesh-s4Onb4OBXuuJl6LPfGQrZpYP-lVHk,84818
52
+ key_manager/providers/modelscope.py,sha256=3DPtPEZ1cyMlWOc_T_B_9JDMndMGAM643TBsPs7My8Y,5603
53
+ key_manager/providers/nvidia.py,sha256=OzDFTBcRerJniXn69MbKfykmagEifwfo8Mlv40IOy4M,4461
54
+ key_manager/providers/ocoolai.py,sha256=kdha8BPLVWphuTOezwkRG4keywqlAMhsnLIR6FTmmHM,4430
55
+ key_manager/providers/openai.py,sha256=ky0ykK0hbqljTWK13Jni9XeltWlFEmb9X3t_FooiPns,5652
56
+ key_manager/providers/openrouter.py,sha256=xiCw7H1stbzjQSixEak4ELkYLWv1LeDD__QcHZ5Z4RI,5262
57
+ key_manager/providers/perplexity.py,sha256=ZUtVQSwYwlVnGBwH3yImJapIkmnC5T82Eb0xs_ZHlhs,4454
58
+ key_manager/providers/poe.py,sha256=rUr_jzt3dH_FODh0qoca3WzKjfa8Dz2zyc0oRGak1AA,4418
59
+ key_manager/providers/ppio.py,sha256=1nq5JHffdWYD0K3sxWfaTd1mGkkxcqVSfbCkjX5NdYc,4453
60
+ key_manager/providers/replicate.py,sha256=TwoiGGS-bIaKPYG_KW9Z92QsTp_xCaUuhNK5rcxGyus,2348
61
+ key_manager/providers/siliconflow.py,sha256=SdlWuyEGYiRFBJavyUBF3SMi2bsSSjOioLd2NGLNxpY,5234
62
+ key_manager/providers/stepfun.py,sha256=JkuZv26G-imVXOGiGLGgbouIWoiWFNvFJUsRUlbklzA,5416
63
+ key_manager/providers/tencent_hunyuan.py,sha256=q5fV8af98qNAovadLe_rDTS11LVcF4FTnE-3Yt8c4oI,5416
64
+ key_manager/providers/together.py,sha256=wD8Z3T_si-9ttMSxYNFxY7t588O1Ok7dPcKmzCPvvxg,5566
65
+ key_manager/providers/yi.py,sha256=krOLqoEV6ytwLV4JqCFMjgI28s6B11NA-TtyiZlfiB0,4135
66
+ key_manager/providers/zai.py,sha256=RkLlhEz9LEs8VoHVhQUhh9o-p9q4lZsSmF1lK1saXyY,4424
67
+ key_manager/providers/zhipu.py,sha256=gUMSyJSzUvLopNHKEM1bn4FBSzRaiSAQPs3itwOrnhY,5665
68
+ key_manager/providers/zhipu_coding.py,sha256=yy4SuKXXpLCAHqZfTInlvZ5tYCOQ1u55EPbZfKVYcNw,5381
69
+ api_key_manager-2.1.0.dist-info/METADATA,sha256=KxMBaY8mYAh4U4UOxmfmTtje7-smlMimsnO1NXYkvGo,21747
70
+ api_key_manager-2.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
71
+ api_key_manager-2.1.0.dist-info/entry_points.txt,sha256=Md42USNyHdYOXdPsW9NYvcZyTl_5rP7aySf8gqQu9tA,57
72
+ api_key_manager-2.1.0.dist-info/top_level.txt,sha256=tCMUyU-fS25OwJkzgCLR5ewI0FzSiO2k6Ui1eqITqAc,12
73
+ api_key_manager-2.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ api-key-manager = key_manager.cli:main
@@ -0,0 +1 @@
1
+ key_manager
@@ -0,0 +1,16 @@
1
+ """Key Manager - Batch API key management for 44+ AI providers."""
2
+ from key_manager.core import KeyManager
3
+ from key_manager.providers import PROVIDERS, get_display_name
4
+ from key_manager.errors import KeyManagerError, ErrorCode
5
+ from key_manager.storage import KeyStore
6
+
7
+ __all__ = [
8
+ "KeyManager",
9
+ "PROVIDERS",
10
+ "get_display_name",
11
+ "KeyManagerError",
12
+ "ErrorCode",
13
+ "KeyStore",
14
+ ]
15
+
16
+ __version__ = "2.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow `python -m key_manager` to run the CLI."""
2
+ from key_manager.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,358 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from key_manager.errors import ErrorResponse
9
+
10
+ __all__ = [
11
+ "ErrorResponse",
12
+ "KeyInfo",
13
+ "KeyListResponse",
14
+ "KeyExportItem",
15
+ "KeyExportResponse",
16
+ "ImportRequest",
17
+ "ImportResponse",
18
+ "CheckSingleRequest",
19
+ "CheckSingleResponse",
20
+ "CheckBatchItem",
21
+ "CheckBatchRequest",
22
+ "CheckBatchResult",
23
+ "CheckBatchSummary",
24
+ "CheckBatchResponse",
25
+ "TestSingleRequest",
26
+ "TestSingleResponse",
27
+ "BalanceRequest",
28
+ "BalanceResponse",
29
+ "ModelInfo",
30
+ "ModelsResponse",
31
+ "ProviderInfo",
32
+ "ProvidersResponse",
33
+ "ProviderDetail",
34
+ "ProviderDetailResponse",
35
+ "StatsProviderEntry",
36
+ "StatsResponse",
37
+ "StatsChartProviderEntry",
38
+ "StatsChartStatuses",
39
+ "StatsChartResponse",
40
+ "LogEntry",
41
+ "LogsResponse",
42
+ "OperationEntry",
43
+ "OperationsResponse",
44
+ "ProgressResponse",
45
+ ]
46
+
47
+
48
+ # ── Keys ──────────────────────────────────────────────────────────────────────
49
+
50
+
51
+ class KeyInfo(BaseModel):
52
+ key_masked: str = Field(..., description="Masked key for display")
53
+ provider: str = Field(..., description="Provider identifier")
54
+ status: str = Field(..., description="Key status: valid / invalid / error / unknown")
55
+ last_checked: str | None = Field(None, description="ISO-8601 timestamp of last check")
56
+ last_error: str | None = Field(None, description="Most recent error message")
57
+ error_type: str | None = Field(None, description="Categorised error type")
58
+ tests: dict[str, Any] = Field(default_factory=dict, description="Test results (max_tokens, max_concurrency)")
59
+ models: list[str] = Field(default_factory=list, description="Available models")
60
+ sources_count: int = Field(0, description="Number of import sources")
61
+ balance: float | None = Field(None, description="Account balance if available")
62
+
63
+
64
+ class KeyListResponse(BaseModel):
65
+ keys: list[KeyInfo] = Field(default_factory=list, description="Paginated key list")
66
+ total: int = Field(0, description="Total number of keys matching filters")
67
+ page: int = Field(1, description="Current page number")
68
+ total_pages: int = Field(0, description="Total number of pages")
69
+ page_size: int = Field(50, description="Page size")
70
+
71
+
72
+ class KeyExportItem(BaseModel):
73
+ provider: str = Field(..., description="Provider identifier")
74
+ max_tokens: int | None = Field(None, description="Maximum token limit")
75
+ max_concurrency: int | None = Field(None, description="Maximum concurrency limit")
76
+
77
+
78
+ class KeyExportResponse(BaseModel):
79
+ keys: list[KeyExportItem] = Field(default_factory=list, description="Exported valid keys")
80
+ total: int = Field(0, description="Total exported key count")
81
+
82
+
83
+ # ── Import ────────────────────────────────────────────────────────────────────
84
+
85
+
86
+ class ImportRequest(BaseModel):
87
+ file: str | None = Field(None, description="Path to a JSON file containing keys")
88
+ directory: str | None = Field(None, description="Directory to scan for key files")
89
+ batch: list[str] | None = Field(None, description="Inline list of keys to import")
90
+
91
+ model_config = {
92
+ "json_schema_extra": {
93
+ "examples": [
94
+ {"file": "./data/input/keys.json"},
95
+ {"batch": ["sk-abc123", "sk-def456"]},
96
+ ]
97
+ }
98
+ }
99
+
100
+
101
+ class ImportResponse(BaseModel):
102
+ new: int = Field(0, description="Number of newly imported keys")
103
+ duplicates: int = Field(0, description="Number of duplicate keys skipped")
104
+ errors: list[str] = Field(default_factory=list, description="Import error messages")
105
+
106
+
107
+ # ── Check ─────────────────────────────────────────────────────────────────────
108
+
109
+
110
+ class CheckSingleRequest(BaseModel):
111
+ key: str = Field(..., min_length=1, description="API key to check")
112
+ provider: str = Field("", description="Provider name (auto-detected if empty)")
113
+ custom_base_url: str | None = Field(None, description="Override provider base URL")
114
+
115
+ model_config = {
116
+ "json_schema_extra": {
117
+ "examples": [{"key": "sk-abc123", "provider": "openai"}]
118
+ }
119
+ }
120
+
121
+
122
+ class CheckSingleResponse(BaseModel):
123
+ key_masked: str = Field(..., description="Masked key for display")
124
+ provider: str = Field(..., description="Provider identifier")
125
+ display_name: str | None = Field(None, description="Human-readable provider name")
126
+ status: str = Field(..., description="Check result: valid / invalid / error / unknown")
127
+ status_code: int | None = Field(None, description="HTTP status code from provider")
128
+ latency_ms: float = Field(0, description="Response latency in milliseconds")
129
+ error: str | None = Field(None, description="Error message if check failed")
130
+ error_type: str | None = Field(None, description="Categorised error type")
131
+ balance: dict[str, Any] | None = Field(None, description="Balance information")
132
+ models: list[str] = Field(default_factory=list, description="Available models")
133
+
134
+
135
+ class CheckBatchItem(BaseModel):
136
+ key: str = Field(..., min_length=1, description="API key to check")
137
+ provider: str = Field("", description="Provider name (auto-detected if empty)")
138
+
139
+
140
+ class CheckBatchRequest(BaseModel):
141
+ keys: list[CheckBatchItem] = Field(..., min_length=1, description="Keys to check")
142
+ timeout: int = Field(10, description="Per-key timeout in seconds")
143
+ concurrency: int = Field(50, description="Maximum concurrent checks")
144
+ custom_base_url: str | None = Field(None, description="Override provider base URL for all keys")
145
+
146
+ model_config = {
147
+ "json_schema_extra": {
148
+ "examples": [
149
+ {
150
+ "keys": [
151
+ {"key": "sk-abc123", "provider": "openai"},
152
+ {"key": "sk-def456", "provider": "deepseek"},
153
+ ],
154
+ "concurrency": 20,
155
+ }
156
+ ]
157
+ }
158
+ }
159
+
160
+
161
+ class CheckBatchResult(BaseModel):
162
+ key_masked: str = Field(..., description="Masked key for display")
163
+ provider: str = Field(..., description="Provider identifier")
164
+ status: str = Field(..., description="Check result: valid / invalid / error / unknown")
165
+ status_code: int | None = Field(None, description="HTTP status code from provider")
166
+ latency_ms: float = Field(0, description="Response latency in milliseconds")
167
+ error: str | None = Field(None, description="Error message if check failed")
168
+ error_type: str | None = Field(None, description="Categorised error type")
169
+ balance: dict[str, Any] | None = Field(None, description="Balance information")
170
+
171
+
172
+ class CheckBatchSummary(BaseModel):
173
+ total: int = Field(0, description="Total keys checked")
174
+ valid: int = Field(0, description="Number of valid keys")
175
+ invalid: int = Field(0, description="Number of invalid keys")
176
+ error: int = Field(0, description="Number of errored keys")
177
+
178
+
179
+ class CheckBatchResponse(BaseModel):
180
+ results: list[CheckBatchResult] = Field(default_factory=list, description="Per-key results")
181
+ summary: CheckBatchSummary = Field(default_factory=CheckBatchSummary, description="Aggregate summary")
182
+
183
+
184
+ # ── Test ──────────────────────────────────────────────────────────────────────
185
+
186
+
187
+ class TestSingleRequest(BaseModel):
188
+ key: str = Field(..., min_length=1, description="API key to test")
189
+ provider: str = Field("", description="Provider name (auto-detected if empty)")
190
+
191
+ model_config = {
192
+ "json_schema_extra": {
193
+ "examples": [{"key": "sk-abc123", "provider": "openai"}]
194
+ }
195
+ }
196
+
197
+
198
+ class TestSingleResponse(BaseModel):
199
+ provider: str = Field(..., description="Provider identifier")
200
+ key_masked: str = Field(..., description="Masked key for display")
201
+ max_tokens: int | None = Field(None, description="Detected maximum token limit")
202
+ max_concurrency: int | None = Field(None, description="Detected maximum concurrency")
203
+ models: list[str] = Field(default_factory=list, description="Available models")
204
+ error: str | None = Field(None, description="Error message if test failed")
205
+
206
+
207
+ # ── Balance ───────────────────────────────────────────────────────────────────
208
+
209
+
210
+ class BalanceRequest(BaseModel):
211
+ key: str = Field(..., min_length=1, description="API key to query balance for")
212
+ provider: str = Field("", description="Provider name (auto-detected if empty)")
213
+ custom_base_url: str | None = Field(None, description="Override provider base URL")
214
+
215
+ model_config = {
216
+ "json_schema_extra": {
217
+ "examples": [{"key": "sk-abc123", "provider": "openai"}]
218
+ }
219
+ }
220
+
221
+
222
+ class BalanceResponse(BaseModel):
223
+ provider: str = Field(..., description="Provider identifier")
224
+ supported: bool = Field(False, description="Whether provider supports balance queries")
225
+ balance: float | None = Field(None, description="Account balance")
226
+ currency: str | None = Field(None, description="Balance currency code")
227
+ key_masked: str | None = Field(None, description="Masked key for display")
228
+ error: str | None = Field(None, description="Error message if query failed")
229
+
230
+
231
+ # ── Models ────────────────────────────────────────────────────────────────────
232
+
233
+
234
+ class ModelInfo(BaseModel):
235
+ model: str = Field(..., description="Model identifier")
236
+ available: bool | None = Field(None, description="Whether the model is available (if checked)")
237
+
238
+
239
+ class ModelsResponse(BaseModel):
240
+ provider: str = Field(..., description="Provider identifier")
241
+ models: list[str] = Field(default_factory=list, description="Available model identifiers")
242
+ total: int = Field(0, description="Total model count")
243
+ type_filter: str = Field("all", description="Applied type filter")
244
+ source: str | None = Field(None, description="Data source: api / static")
245
+ hint: str | None = Field(None, description="Hint message for user")
246
+ error: str | None = Field(None, description="Error message if fetch failed")
247
+
248
+
249
+ # ── Providers ─────────────────────────────────────────────────────────────────
250
+
251
+
252
+ class ProviderInfo(BaseModel):
253
+ name: str = Field(..., description="Provider identifier")
254
+ display_name: str = Field(..., description="Human-readable provider name")
255
+ prefix: str = Field("-", description="Key prefix pattern")
256
+ base_url: str = Field("", description="Provider API base URL")
257
+ type: str = Field("ai", description="Provider type")
258
+
259
+
260
+ class ProvidersResponse(BaseModel):
261
+ providers: list[ProviderInfo] = Field(default_factory=list, description="Registered providers")
262
+ total: int = Field(0, description="Total provider count")
263
+
264
+
265
+ class ProviderDetail(BaseModel):
266
+ name: str = Field(..., description="Provider identifier")
267
+ display_name: str = Field(..., description="Human-readable provider name")
268
+ prefix: str = Field("-", description="Key prefix pattern")
269
+ base_url: str = Field("", description="Provider API base URL")
270
+ website_url: str = Field("", description="Provider website URL")
271
+ docs_url: str = Field("", description="Provider documentation URL")
272
+ website_name: str = Field("", description="Provider website display name")
273
+
274
+
275
+ class ProviderDetailResponse(BaseModel):
276
+ providers: list[ProviderDetail] = Field(default_factory=list, description="Provider details")
277
+ total: int = Field(0, description="Total provider count")
278
+
279
+
280
+ # ── Stats ─────────────────────────────────────────────────────────────────────
281
+
282
+
283
+ class StatsProviderEntry(BaseModel):
284
+ total: int = Field(0, description="Total keys for this provider")
285
+ valid: int = Field(0, description="Valid keys")
286
+ invalid: int = Field(0, description="Invalid keys")
287
+ error: int = Field(0, description="Errored keys")
288
+ display_name: str = Field("", description="Human-readable provider name")
289
+
290
+
291
+ class StatsResponse(BaseModel):
292
+ providers: dict[str, StatsProviderEntry] = Field(
293
+ default_factory=dict, description="Per-provider statistics"
294
+ )
295
+ total: int = Field(0, description="Total key count")
296
+
297
+
298
+ class StatsChartProviderEntry(BaseModel):
299
+ total: int = Field(0, description="Total keys for this provider")
300
+ valid: int = Field(0, description="Valid keys")
301
+ invalid: int = Field(0, description="Invalid keys")
302
+ error: int = Field(0, description="Errored keys")
303
+
304
+
305
+ class StatsChartStatuses(BaseModel):
306
+ valid: int = Field(0, description="Total valid keys")
307
+ invalid: int = Field(0, description="Total invalid keys")
308
+ error: int = Field(0, description="Total errored keys")
309
+ unknown: int = Field(0, description="Total unknown-status keys")
310
+
311
+
312
+ class StatsChartResponse(BaseModel):
313
+ providers: dict[str, StatsChartProviderEntry] = Field(
314
+ default_factory=dict, description="Per-provider breakdown for charts"
315
+ )
316
+ statuses: StatsChartStatuses = Field(
317
+ default_factory=StatsChartStatuses, description="Global status counts"
318
+ )
319
+
320
+
321
+ # ── Logs ──────────────────────────────────────────────────────────────────────
322
+
323
+
324
+ class LogEntry(BaseModel):
325
+ timestamp: str | None = Field(None, description="Log entry timestamp")
326
+ level: str | None = Field(None, description="Log level (INFO, WARNING, ERROR)")
327
+ message: str = Field("", description="Log message content")
328
+ extra: dict[str, Any] | None = Field(None, description="Additional structured data")
329
+
330
+
331
+ class LogsResponse(BaseModel):
332
+ logs: list[LogEntry | str] = Field(default_factory=list, description="Recent log entries")
333
+ total: int = Field(0, description="Number of entries returned")
334
+
335
+
336
+ class OperationEntry(BaseModel):
337
+ timestamp: str | None = Field(None, description="Operation timestamp")
338
+ action: str = Field("", description="Operation type (import, check, test, etc.)")
339
+ detail: str = Field("", description="Operation detail")
340
+ extra: dict[str, Any] | None = Field(None, description="Additional structured data")
341
+
342
+
343
+ class OperationsResponse(BaseModel):
344
+ operations: list[OperationEntry | dict[str, Any]] = Field(
345
+ default_factory=list, description="Structured operations log"
346
+ )
347
+ total: int = Field(0, description="Number of entries returned")
348
+
349
+
350
+ # ── Progress ──────────────────────────────────────────────────────────────────
351
+
352
+
353
+ class ProgressResponse(BaseModel):
354
+ active: bool = Field(False, description="Whether a long-running task is in progress")
355
+ current: int = Field(0, description="Current progress counter")
356
+ total: int = Field(0, description="Total items to process")
357
+ status: str = Field("", description="Task status: loading / done / error")
358
+ results: dict[str, Any] | None = Field(None, description="Final results when complete")
key_manager/checker.py ADDED
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+ import json
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+
8
+ from .providers import PROVIDERS
9
+ from .validator import validate_keys
10
+
11
+
12
+ async def run_check(keys_file: str = "./data/keys.json",
13
+ results_file: str = "./data/check_results.json",
14
+ logs_dir: str = "./data/logs",
15
+ concurrency: int = 100,
16
+ timeout: int = 30,
17
+ proxy: str = None,
18
+ retry_failed: bool = True,
19
+ retry_count: int = 2) -> dict:
20
+ results = await validate_keys(
21
+ keys_file=keys_file,
22
+ results_file=results_file,
23
+ logs_dir=logs_dir,
24
+ concurrency=concurrency,
25
+ timeout=timeout,
26
+ proxy=proxy
27
+ )
28
+
29
+ # Retry failed keys
30
+ if retry_failed and results["summary"]["error"]["count"] > 0:
31
+ for i in range(retry_count):
32
+ error_keys = results["summary"]["error"]["keys"]
33
+ if not error_keys:
34
+ break
35
+
36
+ retry_results = await validate_keys(
37
+ keys_file=keys_file,
38
+ results_file=results_file,
39
+ logs_dir=logs_dir,
40
+ concurrency=concurrency,
41
+ timeout=timeout,
42
+ proxy=proxy,
43
+ status_filter="error"
44
+ )
45
+
46
+ # Merge results
47
+ results["summary"]["valid"]["count"] += retry_results["summary"]["valid"]["count"]
48
+ results["summary"]["invalid"]["count"] += retry_results["summary"]["invalid"]["count"]
49
+ results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
50
+
51
+ return results