cli-anything-cliproxyapi 1.0.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.
- cli_anything/cliproxyapi/__init__.py +3 -0
- cli_anything/cliproxyapi/cliproxyapi_cli.py +1238 -0
- cli_anything/cliproxyapi/core/__init__.py +1 -0
- cli_anything/cliproxyapi/core/api_keys.py +144 -0
- cli_anything/cliproxyapi/core/auth.py +176 -0
- cli_anything/cliproxyapi/core/client.py +101 -0
- cli_anything/cliproxyapi/core/config.py +194 -0
- cli_anything/cliproxyapi/core/logs.py +47 -0
- cli_anything/cliproxyapi/core/models.py +65 -0
- cli_anything/cliproxyapi/core/oauth.py +57 -0
- cli_anything/cliproxyapi/core/proxy.py +145 -0
- cli_anything/cliproxyapi/core/usage.py +25 -0
- cli_anything/cliproxyapi/tests/test_core.py +621 -0
- cli_anything/cliproxyapi/tests/test_full_e2e.py +309 -0
- cli_anything/cliproxyapi/utils/__init__.py +1 -0
- cli_anything/cliproxyapi/utils/output.py +77 -0
- cli_anything_cliproxyapi-1.0.0.dist-info/METADATA +10 -0
- cli_anything_cliproxyapi-1.0.0.dist-info/RECORD +21 -0
- cli_anything_cliproxyapi-1.0.0.dist-info/WHEEL +5 -0
- cli_anything_cliproxyapi-1.0.0.dist-info/entry_points.txt +2 -0
- cli_anything_cliproxyapi-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""模型管理模块。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .client import ManagementClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelManager:
|
|
11
|
+
"""管理 CLIProxyAPI 的模型列表、别名和排除规则。"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client: ManagementClient):
|
|
14
|
+
self.client = client
|
|
15
|
+
|
|
16
|
+
def list_models(self, api_key: str) -> dict:
|
|
17
|
+
resp = self.client.proxy_get("/v1/models", api_key)
|
|
18
|
+
resp.raise_for_status()
|
|
19
|
+
return resp.json()
|
|
20
|
+
|
|
21
|
+
def get_oauth_model_alias(self) -> dict:
|
|
22
|
+
resp = self.client.get("/oauth-model-alias")
|
|
23
|
+
resp.raise_for_status()
|
|
24
|
+
return resp.json()
|
|
25
|
+
|
|
26
|
+
def put_oauth_model_alias(self, aliases: dict) -> dict:
|
|
27
|
+
resp = self.client.put("/oauth-model-alias", json_data=aliases)
|
|
28
|
+
resp.raise_for_status()
|
|
29
|
+
return resp.json()
|
|
30
|
+
|
|
31
|
+
def patch_oauth_model_alias(self, channel: str, entry: dict) -> dict:
|
|
32
|
+
resp = self.client.patch("/oauth-model-alias", json_data={"channel": channel, **entry})
|
|
33
|
+
resp.raise_for_status()
|
|
34
|
+
return resp.json()
|
|
35
|
+
|
|
36
|
+
def delete_oauth_model_alias(self, channel: str, name: str = "") -> dict:
|
|
37
|
+
data: dict[str, Any] = {"channel": channel}
|
|
38
|
+
if name:
|
|
39
|
+
data["name"] = name
|
|
40
|
+
resp = self.client.delete("/oauth-model-alias", json_data=data)
|
|
41
|
+
resp.raise_for_status()
|
|
42
|
+
return resp.json()
|
|
43
|
+
|
|
44
|
+
def get_oauth_excluded_models(self) -> dict:
|
|
45
|
+
resp = self.client.get("/oauth-excluded-models")
|
|
46
|
+
resp.raise_for_status()
|
|
47
|
+
return resp.json()
|
|
48
|
+
|
|
49
|
+
def put_oauth_excluded_models(self, exclusions: dict) -> dict:
|
|
50
|
+
resp = self.client.put("/oauth-excluded-models", json_data=exclusions)
|
|
51
|
+
resp.raise_for_status()
|
|
52
|
+
return resp.json()
|
|
53
|
+
|
|
54
|
+
def patch_oauth_excluded_models(self, channel: str, models: list) -> dict:
|
|
55
|
+
resp = self.client.patch("/oauth-excluded-models", json_data={"channel": channel, "models": models})
|
|
56
|
+
resp.raise_for_status()
|
|
57
|
+
return resp.json()
|
|
58
|
+
|
|
59
|
+
def delete_oauth_excluded_models(self, channel: str, model: str = "") -> dict:
|
|
60
|
+
data: dict[str, Any] = {"channel": channel}
|
|
61
|
+
if model:
|
|
62
|
+
data["model"] = model
|
|
63
|
+
resp = self.client.delete("/oauth-excluded-models", json_data=data)
|
|
64
|
+
resp.raise_for_status()
|
|
65
|
+
return resp.json()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""OAuth 登录流程模块。"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .client import ManagementClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OAuthManager:
|
|
9
|
+
"""管理 CLIProxyAPI 的 OAuth 登录流程。"""
|
|
10
|
+
|
|
11
|
+
PROVIDERS = {
|
|
12
|
+
"anthropic": {"path": "/anthropic-auth-url", "label": "Claude"},
|
|
13
|
+
"codex": {"path": "/codex-auth-url", "label": "Codex"},
|
|
14
|
+
"gemini": {"path": "/gemini-cli-auth-url", "label": "Gemini CLI"},
|
|
15
|
+
"antigravity": {"path": "/antigravity-auth-url", "label": "Antigravity"},
|
|
16
|
+
"qwen": {"path": "/qwen-auth-url", "label": "Qwen Code"},
|
|
17
|
+
"kimi": {"path": "/kimi-auth-url", "label": "Kimi"},
|
|
18
|
+
"iflow": {"path": "/iflow-auth-url", "label": "iFlow"},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: ManagementClient):
|
|
22
|
+
self.client = client
|
|
23
|
+
|
|
24
|
+
def request_auth_url(self, provider: str, no_browser: bool = False) -> dict:
|
|
25
|
+
if provider not in self.PROVIDERS:
|
|
26
|
+
raise ValueError(f"不支持提供商: {provider},可选: {', '.join(self.PROVIDERS)}")
|
|
27
|
+
|
|
28
|
+
info = self.PROVIDERS[provider]
|
|
29
|
+
params = {}
|
|
30
|
+
if no_browser:
|
|
31
|
+
params["no_browser"] = "true"
|
|
32
|
+
|
|
33
|
+
if provider == "iflow":
|
|
34
|
+
resp = self.client.get(info["path"], params=params)
|
|
35
|
+
else:
|
|
36
|
+
resp = self.client.get(info["path"], params=params)
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
return resp.json()
|
|
39
|
+
|
|
40
|
+
def request_iflow_cookie(self, cookie: str) -> dict:
|
|
41
|
+
resp = self.client.post("/iflow-auth-url", json_data={"cookie": cookie})
|
|
42
|
+
resp.raise_for_status()
|
|
43
|
+
return resp.json()
|
|
44
|
+
|
|
45
|
+
def post_oauth_callback(self, provider: str, code: str, state: str) -> dict:
|
|
46
|
+
resp = self.client.post("/oauth-callback", json_data={
|
|
47
|
+
"provider": provider,
|
|
48
|
+
"code": code,
|
|
49
|
+
"state": state,
|
|
50
|
+
})
|
|
51
|
+
resp.raise_for_status()
|
|
52
|
+
return resp.json()
|
|
53
|
+
|
|
54
|
+
def get_auth_status(self, session_id: str) -> dict:
|
|
55
|
+
resp = self.client.get("/get-auth-status", params={"session_id": session_id})
|
|
56
|
+
resp.raise_for_status()
|
|
57
|
+
return resp.json()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""代理/服务器管理模块。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .client import ManagementClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProxyManager:
|
|
11
|
+
"""管理 CLIProxyAPI 的代理设置和 Amp 集成。"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client: ManagementClient):
|
|
14
|
+
self.client = client
|
|
15
|
+
|
|
16
|
+
# ---- 服务器状态 ----
|
|
17
|
+
|
|
18
|
+
def health_check(self) -> dict:
|
|
19
|
+
resp = self.client.health_check()
|
|
20
|
+
resp.raise_for_status()
|
|
21
|
+
return resp.json()
|
|
22
|
+
|
|
23
|
+
# ---- Amp 集成 ----
|
|
24
|
+
|
|
25
|
+
def get_amp_config(self) -> dict:
|
|
26
|
+
resp = self.client.get("/ampcode")
|
|
27
|
+
resp.raise_for_status()
|
|
28
|
+
return resp.json()
|
|
29
|
+
|
|
30
|
+
def get_amp_upstream_url(self) -> Optional[str]:
|
|
31
|
+
resp = self.client.get("/ampcode/upstream-url")
|
|
32
|
+
resp.raise_for_status()
|
|
33
|
+
return resp.json().get("value")
|
|
34
|
+
|
|
35
|
+
def set_amp_upstream_url(self, url: str) -> dict:
|
|
36
|
+
resp = self.client.put("/ampcode/upstream-url", json_data={"value": url})
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
return resp.json()
|
|
39
|
+
|
|
40
|
+
def delete_amp_upstream_url(self) -> dict:
|
|
41
|
+
resp = self.client.delete("/ampcode/upstream-url")
|
|
42
|
+
resp.raise_for_status()
|
|
43
|
+
return resp.json()
|
|
44
|
+
|
|
45
|
+
def get_amp_upstream_api_key(self) -> Optional[str]:
|
|
46
|
+
resp = self.client.get("/ampcode/upstream-api-key")
|
|
47
|
+
resp.raise_for_status()
|
|
48
|
+
return resp.json().get("value")
|
|
49
|
+
|
|
50
|
+
def set_amp_upstream_api_key(self, key: str) -> dict:
|
|
51
|
+
resp = self.client.put("/ampcode/upstream-api-key", json_data={"value": key})
|
|
52
|
+
resp.raise_for_status()
|
|
53
|
+
return resp.json()
|
|
54
|
+
|
|
55
|
+
def delete_amp_upstream_api_key(self) -> dict:
|
|
56
|
+
resp = self.client.delete("/ampcode/upstream-api-key")
|
|
57
|
+
resp.raise_for_status()
|
|
58
|
+
return resp.json()
|
|
59
|
+
|
|
60
|
+
def get_amp_model_mappings(self) -> list:
|
|
61
|
+
resp = self.client.get("/ampcode/model-mappings")
|
|
62
|
+
resp.raise_for_status()
|
|
63
|
+
return resp.json().get("value", [])
|
|
64
|
+
|
|
65
|
+
def set_amp_model_mappings(self, mappings: list) -> dict:
|
|
66
|
+
resp = self.client.put("/ampcode/model-mappings", json_data={"value": mappings})
|
|
67
|
+
resp.raise_for_status()
|
|
68
|
+
return resp.json()
|
|
69
|
+
|
|
70
|
+
def patch_amp_model_mappings(self, mapping: dict) -> dict:
|
|
71
|
+
resp = self.client.patch("/ampcode/model-mappings", json_data=mapping)
|
|
72
|
+
resp.raise_for_status()
|
|
73
|
+
return resp.json()
|
|
74
|
+
|
|
75
|
+
def delete_amp_model_mappings(self, from_model: str = "") -> dict:
|
|
76
|
+
data: dict[str, Any] = {}
|
|
77
|
+
if from_model:
|
|
78
|
+
data["from"] = from_model
|
|
79
|
+
resp = self.client.delete("/ampcode/model-mappings", json_data=data)
|
|
80
|
+
resp.raise_for_status()
|
|
81
|
+
return resp.json()
|
|
82
|
+
|
|
83
|
+
def get_amp_force_model_mappings(self) -> bool:
|
|
84
|
+
resp = self.client.get("/ampcode/force-model-mappings")
|
|
85
|
+
resp.raise_for_status()
|
|
86
|
+
return resp.json().get("value", False)
|
|
87
|
+
|
|
88
|
+
def set_amp_force_model_mappings(self, enabled: bool) -> dict:
|
|
89
|
+
resp = self.client.put("/ampcode/force-model-mappings", json_data={"value": enabled})
|
|
90
|
+
resp.raise_for_status()
|
|
91
|
+
return resp.json()
|
|
92
|
+
|
|
93
|
+
def get_amp_restrict_management_to_localhost(self) -> bool:
|
|
94
|
+
resp = self.client.get("/ampcode/restrict-management-to-localhost")
|
|
95
|
+
resp.raise_for_status()
|
|
96
|
+
return resp.json().get("value", False)
|
|
97
|
+
|
|
98
|
+
def set_amp_restrict_management_to_localhost(self, enabled: bool) -> dict:
|
|
99
|
+
resp = self.client.put("/ampcode/restrict-management-to-localhost", json_data={"value": enabled})
|
|
100
|
+
resp.raise_for_status()
|
|
101
|
+
return resp.json()
|
|
102
|
+
|
|
103
|
+
def get_amp_upstream_api_keys(self) -> list:
|
|
104
|
+
resp = self.client.get("/ampcode/upstream-api-keys")
|
|
105
|
+
resp.raise_for_status()
|
|
106
|
+
return resp.json().get("value", [])
|
|
107
|
+
|
|
108
|
+
def set_amp_upstream_api_keys(self, keys: list) -> dict:
|
|
109
|
+
resp = self.client.put("/ampcode/upstream-api-keys", json_data={"value": keys})
|
|
110
|
+
resp.raise_for_status()
|
|
111
|
+
return resp.json()
|
|
112
|
+
|
|
113
|
+
def patch_amp_upstream_api_keys(self, entry: dict) -> dict:
|
|
114
|
+
resp = self.client.patch("/ampcode/upstream-api-keys", json_data=entry)
|
|
115
|
+
resp.raise_for_status()
|
|
116
|
+
return resp.json()
|
|
117
|
+
|
|
118
|
+
def delete_amp_upstream_api_keys(self, upstream_api_key: str = "") -> dict:
|
|
119
|
+
data: dict[str, Any] = {}
|
|
120
|
+
if upstream_api_key:
|
|
121
|
+
data["upstream_api_key"] = upstream_api_key
|
|
122
|
+
resp = self.client.delete("/ampcode/upstream-api-keys", json_data=data)
|
|
123
|
+
resp.raise_for_status()
|
|
124
|
+
return resp.json()
|
|
125
|
+
|
|
126
|
+
# ---- 通用 API 调用 ----
|
|
127
|
+
|
|
128
|
+
def api_call(
|
|
129
|
+
self,
|
|
130
|
+
method: str,
|
|
131
|
+
url: str,
|
|
132
|
+
headers: Optional[dict] = None,
|
|
133
|
+
data: Optional[str] = None,
|
|
134
|
+
auth_index: Optional[str] = None,
|
|
135
|
+
) -> dict:
|
|
136
|
+
body: dict[str, Any] = {"method": method, "url": url}
|
|
137
|
+
if headers:
|
|
138
|
+
body["header"] = headers
|
|
139
|
+
if data:
|
|
140
|
+
body["data"] = data
|
|
141
|
+
if auth_index:
|
|
142
|
+
body["auth_index"] = auth_index
|
|
143
|
+
resp = self.client.post("/api-call", json_data=body)
|
|
144
|
+
resp.raise_for_status()
|
|
145
|
+
return resp.json()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""使用统计模块。"""
|
|
2
|
+
|
|
3
|
+
from .client import ManagementClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UsageManager:
|
|
7
|
+
"""管理 CLIProxyAPI 的使用统计。"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client: ManagementClient):
|
|
10
|
+
self.client = client
|
|
11
|
+
|
|
12
|
+
def get_stats(self) -> dict:
|
|
13
|
+
resp = self.client.get("/usage")
|
|
14
|
+
resp.raise_for_status()
|
|
15
|
+
return resp.json()
|
|
16
|
+
|
|
17
|
+
def export_stats(self) -> str:
|
|
18
|
+
resp = self.client.get("/usage/export")
|
|
19
|
+
resp.raise_for_status()
|
|
20
|
+
return resp.text
|
|
21
|
+
|
|
22
|
+
def import_stats(self, data: str) -> dict:
|
|
23
|
+
resp = self.client.post("/usage/import", json_data={"data": data})
|
|
24
|
+
resp.raise_for_status()
|
|
25
|
+
return resp.json()
|