coding-proxy 0.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.
- coding/__init__.py +0 -0
- coding/proxy/__init__.py +3 -0
- coding/proxy/__main__.py +5 -0
- coding/proxy/auth/__init__.py +13 -0
- coding/proxy/auth/providers/__init__.py +6 -0
- coding/proxy/auth/providers/base.py +35 -0
- coding/proxy/auth/providers/github.py +133 -0
- coding/proxy/auth/providers/google.py +237 -0
- coding/proxy/auth/runtime.py +122 -0
- coding/proxy/auth/store.py +74 -0
- coding/proxy/cli/__init__.py +151 -0
- coding/proxy/cli/auth_commands.py +224 -0
- coding/proxy/compat/__init__.py +30 -0
- coding/proxy/compat/canonical.py +193 -0
- coding/proxy/compat/session_store.py +137 -0
- coding/proxy/config/__init__.py +6 -0
- coding/proxy/config/auth_schema.py +24 -0
- coding/proxy/config/loader.py +139 -0
- coding/proxy/config/resiliency.py +46 -0
- coding/proxy/config/routing.py +279 -0
- coding/proxy/config/schema.py +280 -0
- coding/proxy/config/server.py +23 -0
- coding/proxy/config/vendors.py +53 -0
- coding/proxy/convert/__init__.py +14 -0
- coding/proxy/convert/anthropic_to_gemini.py +352 -0
- coding/proxy/convert/anthropic_to_openai.py +352 -0
- coding/proxy/convert/gemini_sse_adapter.py +169 -0
- coding/proxy/convert/gemini_to_anthropic.py +98 -0
- coding/proxy/convert/openai_to_anthropic.py +88 -0
- coding/proxy/logging/__init__.py +49 -0
- coding/proxy/logging/db.py +308 -0
- coding/proxy/logging/stats.py +129 -0
- coding/proxy/model/__init__.py +93 -0
- coding/proxy/model/auth.py +32 -0
- coding/proxy/model/compat.py +153 -0
- coding/proxy/model/constants.py +21 -0
- coding/proxy/model/pricing.py +70 -0
- coding/proxy/model/token.py +64 -0
- coding/proxy/model/vendor.py +218 -0
- coding/proxy/pricing.py +100 -0
- coding/proxy/routing/__init__.py +47 -0
- coding/proxy/routing/circuit_breaker.py +152 -0
- coding/proxy/routing/error_classifier.py +67 -0
- coding/proxy/routing/executor.py +453 -0
- coding/proxy/routing/model_mapper.py +90 -0
- coding/proxy/routing/quota_guard.py +169 -0
- coding/proxy/routing/rate_limit.py +159 -0
- coding/proxy/routing/retry.py +82 -0
- coding/proxy/routing/router.py +84 -0
- coding/proxy/routing/session_manager.py +62 -0
- coding/proxy/routing/tier.py +171 -0
- coding/proxy/routing/usage_parser.py +193 -0
- coding/proxy/routing/usage_recorder.py +131 -0
- coding/proxy/server/__init__.py +1 -0
- coding/proxy/server/app.py +142 -0
- coding/proxy/server/factory.py +175 -0
- coding/proxy/server/request_normalizer.py +139 -0
- coding/proxy/server/responses.py +74 -0
- coding/proxy/server/routes.py +264 -0
- coding/proxy/streaming/__init__.py +1 -0
- coding/proxy/streaming/anthropic_compat.py +484 -0
- coding/proxy/vendors/__init__.py +29 -0
- coding/proxy/vendors/anthropic.py +44 -0
- coding/proxy/vendors/antigravity.py +328 -0
- coding/proxy/vendors/base.py +353 -0
- coding/proxy/vendors/copilot.py +702 -0
- coding/proxy/vendors/copilot_models.py +438 -0
- coding/proxy/vendors/copilot_token_manager.py +167 -0
- coding/proxy/vendors/copilot_urls.py +16 -0
- coding/proxy/vendors/mixins.py +71 -0
- coding/proxy/vendors/token_manager.py +128 -0
- coding/proxy/vendors/zhipu.py +243 -0
- coding_proxy-0.1.0.dist-info/METADATA +184 -0
- coding_proxy-0.1.0.dist-info/RECORD +77 -0
- coding_proxy-0.1.0.dist-info/WHEEL +4 -0
- coding_proxy-0.1.0.dist-info/entry_points.txt +2 -0
- coding_proxy-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Token Manager 抽象基类 — DCL 缓存机制提取.
|
|
2
|
+
|
|
3
|
+
类型定义(``TokenErrorKind`` / ``TokenAcquireError`` / ``TokenManagerDiagnostics``)
|
|
4
|
+
已迁移至 :mod:`coding.proxy.model.token`。本文件保留 ``BaseTokenManager``
|
|
5
|
+
抽象基类,类型通过 re-export 提供。
|
|
6
|
+
|
|
7
|
+
.. deprecated::
|
|
8
|
+
未来版本将移除类型 re-export,请直接从 :mod:`coding.proxy.model.token` 导入。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import logging
|
|
15
|
+
import time
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
# noqa: F401
|
|
21
|
+
from ..model.token import (
|
|
22
|
+
TokenAcquireError,
|
|
23
|
+
TokenErrorKind,
|
|
24
|
+
TokenManagerDiagnostics,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseTokenManager(ABC):
|
|
31
|
+
"""Token 缓存与自动刷新的通用机制.
|
|
32
|
+
|
|
33
|
+
子类只需实现 ``_acquire()`` 返回 ``(access_token, expires_in_seconds)``。
|
|
34
|
+
|
|
35
|
+
机制层提供:
|
|
36
|
+
- Double-Check Locking (DCL) 并发安全
|
|
37
|
+
- 惰性 httpx 客户端创建
|
|
38
|
+
- ``get_token()`` / ``invalidate()`` / ``close()`` 标准生命周期
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_REFRESH_MARGIN: int = 60 # 提前刷新余量(秒),子类可覆写
|
|
42
|
+
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self._access_token: str | None = None
|
|
45
|
+
self._expires_at: float = 0.0
|
|
46
|
+
self._lock = asyncio.Lock()
|
|
47
|
+
self._client: httpx.AsyncClient | None = None
|
|
48
|
+
self._diagnostics: TokenManagerDiagnostics = TokenManagerDiagnostics()
|
|
49
|
+
|
|
50
|
+
def _get_client(self) -> httpx.AsyncClient:
|
|
51
|
+
if self._client is None or self._client.is_closed:
|
|
52
|
+
self._client = httpx.AsyncClient(timeout=httpx.Timeout(30.0))
|
|
53
|
+
return self._client
|
|
54
|
+
|
|
55
|
+
async def get_token(self) -> str:
|
|
56
|
+
"""获取有效 token(带缓存和自动刷新).
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
TokenAcquireError: 获取失败
|
|
60
|
+
"""
|
|
61
|
+
if self._access_token and time.monotonic() < self._expires_at:
|
|
62
|
+
return self._access_token
|
|
63
|
+
|
|
64
|
+
async with self._lock:
|
|
65
|
+
# Double-check after acquiring lock
|
|
66
|
+
if self._access_token and time.monotonic() < self._expires_at:
|
|
67
|
+
return self._access_token
|
|
68
|
+
try:
|
|
69
|
+
token, expires_in = await self._acquire()
|
|
70
|
+
except TokenAcquireError as exc:
|
|
71
|
+
self._record_error(exc)
|
|
72
|
+
raise
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
wrapped = TokenAcquireError(f"Token 获取异常: {exc}")
|
|
75
|
+
self._record_error(wrapped)
|
|
76
|
+
raise wrapped from exc
|
|
77
|
+
self._access_token = token
|
|
78
|
+
self._expires_at = time.monotonic() + expires_in - self._REFRESH_MARGIN
|
|
79
|
+
self._clear_error()
|
|
80
|
+
return self._access_token
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
async def _acquire(self) -> tuple[str, float]:
|
|
84
|
+
"""获取新 token.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
(access_token, expires_in_seconds) 元组
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
TokenAcquireError: 获取失败,needs_reauth=True 表示需要重新登录
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def invalidate(self) -> None:
|
|
94
|
+
"""标记当前 token 失效(触发下次请求时被动刷新)."""
|
|
95
|
+
self._expires_at = 0.0
|
|
96
|
+
|
|
97
|
+
def get_diagnostics(self) -> dict[str, str | bool]:
|
|
98
|
+
return self._diagnostics.to_dict()
|
|
99
|
+
|
|
100
|
+
def mark_error(
|
|
101
|
+
self,
|
|
102
|
+
message: str,
|
|
103
|
+
*,
|
|
104
|
+
kind: TokenErrorKind = TokenErrorKind.TEMPORARY,
|
|
105
|
+
needs_reauth: bool = False,
|
|
106
|
+
) -> None:
|
|
107
|
+
self._record_error(TokenAcquireError.with_kind(
|
|
108
|
+
message, kind=kind, needs_reauth=needs_reauth,
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
def clear_diagnostics(self) -> None:
|
|
112
|
+
self._clear_error()
|
|
113
|
+
|
|
114
|
+
def _record_error(self, exc: TokenAcquireError) -> None:
|
|
115
|
+
self._diagnostics = TokenManagerDiagnostics(
|
|
116
|
+
last_error=str(exc),
|
|
117
|
+
needs_reauth=exc.needs_reauth,
|
|
118
|
+
error_kind=exc.kind.value,
|
|
119
|
+
updated_at=time.time(),
|
|
120
|
+
)
|
|
121
|
+
logger.warning("Token acquire failed: %s", exc)
|
|
122
|
+
|
|
123
|
+
def _clear_error(self) -> None:
|
|
124
|
+
self._diagnostics = TokenManagerDiagnostics()
|
|
125
|
+
|
|
126
|
+
async def close(self) -> None:
|
|
127
|
+
if self._client and not self._client.is_closed:
|
|
128
|
+
await self._client.aclose()
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""智谱 GLM 供应商 — 原生 Anthropic 兼容端点薄透传代理.
|
|
2
|
+
|
|
3
|
+
官方端点 (https://open.bigmodel.cn/api/anthropic) 已完整支持
|
|
4
|
+
Anthropic Messages API 协议,本模块仅做两项最小适配:
|
|
5
|
+
1. 模型名映射(Claude -> GLM)
|
|
6
|
+
2. 认证头替换(x-api-key)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import copy
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Any, AsyncIterator
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from ..config.schema import ZhipuConfig
|
|
19
|
+
from ..routing.model_mapper import ModelMapper
|
|
20
|
+
from .base import PROXY_SKIP_HEADERS, BaseVendor, VendorCapabilities, VendorResponse
|
|
21
|
+
from .base import _sanitize_headers_for_synthetic_response
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ZhipuVendor(BaseVendor):
|
|
27
|
+
"""智谱 GLM 原生 Anthropic 兼容端点供应商(薄透传).
|
|
28
|
+
|
|
29
|
+
通过官方 /api/anthropic 端点转发请求,
|
|
30
|
+
仅替换模型名和认证头,其余原样透传。
|
|
31
|
+
|
|
32
|
+
401 错误归一化通过基类响应处理钩子(_normalize_error_response)实现,
|
|
33
|
+
无需完整覆写 send_message / send_message_stream。
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
config: ZhipuConfig,
|
|
39
|
+
model_mapper: ModelMapper,
|
|
40
|
+
) -> None:
|
|
41
|
+
super().__init__(config.base_url, config.timeout_ms)
|
|
42
|
+
self._api_key = config.api_key
|
|
43
|
+
self._model_mapper = model_mapper
|
|
44
|
+
|
|
45
|
+
def get_name(self) -> str:
|
|
46
|
+
return "zhipu"
|
|
47
|
+
|
|
48
|
+
def get_capabilities(self) -> VendorCapabilities:
|
|
49
|
+
return VendorCapabilities(
|
|
50
|
+
supports_tools=True,
|
|
51
|
+
supports_thinking=True,
|
|
52
|
+
supports_images=True,
|
|
53
|
+
emits_vendor_tool_events=False,
|
|
54
|
+
supports_metadata=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def get_compatibility_profile(self):
|
|
58
|
+
from ..compat.canonical import CompatibilityProfile, CompatibilityStatus
|
|
59
|
+
|
|
60
|
+
return CompatibilityProfile(
|
|
61
|
+
thinking=CompatibilityStatus.NATIVE,
|
|
62
|
+
tool_calling=CompatibilityStatus.NATIVE,
|
|
63
|
+
tool_streaming=CompatibilityStatus.NATIVE,
|
|
64
|
+
mcp_tools=CompatibilityStatus.NATIVE,
|
|
65
|
+
images=CompatibilityStatus.NATIVE,
|
|
66
|
+
metadata=CompatibilityStatus.NATIVE,
|
|
67
|
+
json_output=CompatibilityStatus.NATIVE,
|
|
68
|
+
usage_tokens=CompatibilityStatus.NATIVE,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def map_model(self, model: str) -> str:
|
|
72
|
+
"""将 Claude 模型名映射为智谱模型名,完全委托 ModelMapper."""
|
|
73
|
+
return self._model_mapper.map(model, vendor="zhipu")
|
|
74
|
+
|
|
75
|
+
async def _prepare_request(
|
|
76
|
+
self,
|
|
77
|
+
request_body: dict[str, Any],
|
|
78
|
+
headers: dict[str, str],
|
|
79
|
+
) -> tuple[dict[str, Any], dict[str, str]]:
|
|
80
|
+
"""深拷贝请求体、映射模型名、替换认证头.
|
|
81
|
+
|
|
82
|
+
其余字段(tools, thinking, metadata, system 等)原样透传。
|
|
83
|
+
"""
|
|
84
|
+
body = copy.deepcopy(request_body)
|
|
85
|
+
|
|
86
|
+
if "model" in body:
|
|
87
|
+
body["model"] = self.map_model(body["model"])
|
|
88
|
+
|
|
89
|
+
# 剥离原始认证头(authorization / x-api-key),由下方 new_headers 重建
|
|
90
|
+
filtered = {
|
|
91
|
+
k: v for k, v in headers.items()
|
|
92
|
+
if k.lower() not in PROXY_SKIP_HEADERS
|
|
93
|
+
and k.lower() not in ("x-api-key", "authorization")
|
|
94
|
+
}
|
|
95
|
+
new_headers = {
|
|
96
|
+
"content-type": "application/json",
|
|
97
|
+
"x-api-key": self._api_key,
|
|
98
|
+
"anthropic-version": headers.get("anthropic-version", "2023-06-01"),
|
|
99
|
+
}
|
|
100
|
+
for key, value in filtered.items():
|
|
101
|
+
if key.lower() not in {item.lower() for item in new_headers}:
|
|
102
|
+
new_headers[key] = value
|
|
103
|
+
return body, new_headers
|
|
104
|
+
|
|
105
|
+
# ── 响应处理钩子 ──────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
async def send_message(
|
|
108
|
+
self,
|
|
109
|
+
request_body: dict[str, Any],
|
|
110
|
+
headers: dict[str, str],
|
|
111
|
+
) -> VendorResponse:
|
|
112
|
+
"""最小化覆写:API key 缺失时快速返回 401 响应,其余委托基类(含 _normalize_error_response 钩子)."""
|
|
113
|
+
if not self._api_key:
|
|
114
|
+
raw = json.dumps(self._missing_api_key_payload(), ensure_ascii=False).encode()
|
|
115
|
+
return VendorResponse(
|
|
116
|
+
status_code=401,
|
|
117
|
+
raw_body=raw,
|
|
118
|
+
error_type="authentication_error",
|
|
119
|
+
error_message="Zhipu API key 未配置,无法访问 Claude 兼容端点",
|
|
120
|
+
response_headers={"content-type": "application/json"},
|
|
121
|
+
)
|
|
122
|
+
return await super().send_message(request_body, headers)
|
|
123
|
+
|
|
124
|
+
def _normalize_error_response(
|
|
125
|
+
self,
|
|
126
|
+
status_code: int,
|
|
127
|
+
response: httpx.Response,
|
|
128
|
+
backend_resp: VendorResponse,
|
|
129
|
+
) -> VendorResponse:
|
|
130
|
+
"""仅对 401 错误执行归一化,其余状态码透传."""
|
|
131
|
+
if status_code != 401:
|
|
132
|
+
return backend_resp
|
|
133
|
+
raw_body, payload = self._normalize_backend_error(
|
|
134
|
+
status_code,
|
|
135
|
+
response.content if response else backend_resp.raw_body,
|
|
136
|
+
)
|
|
137
|
+
error = payload.get("error", {}) if isinstance(payload, dict) else {}
|
|
138
|
+
return VendorResponse(
|
|
139
|
+
status_code=status_code,
|
|
140
|
+
raw_body=raw_body,
|
|
141
|
+
error_type=error.get("type") if isinstance(error, dict) else "authentication_error",
|
|
142
|
+
error_message=error.get("message") if isinstance(error, dict) else "Zhipu API 认证失败",
|
|
143
|
+
response_headers=backend_resp.response_headers,
|
|
144
|
+
usage=backend_resp.usage,
|
|
145
|
+
model_served=backend_resp.model_served,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# ── 流式 401 归一化包装 ───────────────────────────────
|
|
149
|
+
|
|
150
|
+
async def send_message_stream(
|
|
151
|
+
self,
|
|
152
|
+
request_body: dict[str, Any],
|
|
153
|
+
headers: dict[str, str],
|
|
154
|
+
) -> AsyncIterator[bytes]:
|
|
155
|
+
"""轻量包装:API key 缺失时快速失败,否则委托基类流式发送并捕获 401 归一化."""
|
|
156
|
+
if not self._api_key:
|
|
157
|
+
payload = self._missing_api_key_payload()
|
|
158
|
+
raw = json.dumps(payload, ensure_ascii=False).encode()
|
|
159
|
+
request = httpx.Request("POST", f"{self._base_url}{self._get_endpoint()}")
|
|
160
|
+
raise httpx.HTTPStatusError(
|
|
161
|
+
"zhipu API error: 401",
|
|
162
|
+
request=request,
|
|
163
|
+
response=httpx.Response(401, content=raw, headers={"content-type": "application/json"}, request=request),
|
|
164
|
+
)
|
|
165
|
+
try:
|
|
166
|
+
async for chunk in super().send_message_stream(request_body, headers):
|
|
167
|
+
yield chunk
|
|
168
|
+
except httpx.HTTPStatusError as exc:
|
|
169
|
+
response = exc.response
|
|
170
|
+
if response is not None and response.status_code == 401:
|
|
171
|
+
raw_body = response.content or b"{}"
|
|
172
|
+
normalized_raw, _ = self._normalize_backend_error(
|
|
173
|
+
response.status_code,
|
|
174
|
+
raw_body,
|
|
175
|
+
)
|
|
176
|
+
raise httpx.HTTPStatusError(
|
|
177
|
+
str(exc),
|
|
178
|
+
request=exc.request,
|
|
179
|
+
response=httpx.Response(
|
|
180
|
+
response.status_code,
|
|
181
|
+
content=normalized_raw,
|
|
182
|
+
headers=dict(response.headers),
|
|
183
|
+
request=exc.request,
|
|
184
|
+
),
|
|
185
|
+
) from exc
|
|
186
|
+
raise
|
|
187
|
+
|
|
188
|
+
# ── 401 错误归一化工具方法 ────────────────────────────
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _normalize_auth_error_payload(payload: dict[str, Any] | None) -> dict[str, Any]:
|
|
192
|
+
if not isinstance(payload, dict):
|
|
193
|
+
return {
|
|
194
|
+
"error": {
|
|
195
|
+
"type": "authentication_error",
|
|
196
|
+
"message": "Zhipu API 认证失败,请检查 api_key 或兼容端点权限",
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
error = payload.get("error")
|
|
200
|
+
if not isinstance(error, dict):
|
|
201
|
+
payload["error"] = {
|
|
202
|
+
"type": "authentication_error",
|
|
203
|
+
"message": "Zhipu API 认证失败,请检查 api_key 或兼容端点权限",
|
|
204
|
+
}
|
|
205
|
+
return payload
|
|
206
|
+
payload["error"] = {
|
|
207
|
+
**error,
|
|
208
|
+
"type": "authentication_error",
|
|
209
|
+
"message": str(error.get("message") or "Zhipu API 认证失败,请检查 api_key 或兼容端点权限"),
|
|
210
|
+
}
|
|
211
|
+
return payload
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _missing_api_key_payload() -> dict[str, Any]:
|
|
215
|
+
return {
|
|
216
|
+
"error": {
|
|
217
|
+
"type": "authentication_error",
|
|
218
|
+
"message": "Zhipu API key 未配置,无法访问 Claude 兼容端点",
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
def _normalize_backend_error(
|
|
223
|
+
self,
|
|
224
|
+
status_code: int,
|
|
225
|
+
raw_body: bytes,
|
|
226
|
+
) -> tuple[bytes, dict[str, Any] | None]:
|
|
227
|
+
payload: dict[str, Any] | None = None
|
|
228
|
+
if raw_body:
|
|
229
|
+
try:
|
|
230
|
+
decoded = json.loads(raw_body)
|
|
231
|
+
if isinstance(decoded, dict):
|
|
232
|
+
payload = decoded
|
|
233
|
+
except (json.JSONDecodeError, UnicodeDecodeError, TypeError):
|
|
234
|
+
payload = None
|
|
235
|
+
if status_code != 401:
|
|
236
|
+
return raw_body, payload
|
|
237
|
+
payload = self._normalize_auth_error_payload(payload)
|
|
238
|
+
body = copy.deepcopy(payload)
|
|
239
|
+
return json.dumps(body, ensure_ascii=False).encode(), body
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# 向后兼容别名
|
|
243
|
+
ZhipuBackend = ZhipuVendor
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: coding-proxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Claude Code 多后端智能代理 — 自动故障转移与 Token 用量追踪
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: aiosqlite>=0.22
|
|
8
|
+
Requires-Dist: fastapi>=0.135
|
|
9
|
+
Requires-Dist: httpx>=0.28
|
|
10
|
+
Requires-Dist: pydantic>=2.12
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Requires-Dist: rich>=14.3
|
|
13
|
+
Requires-Dist: typer>=0.24
|
|
14
|
+
Requires-Dist: uvicorn>=0.42
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
[English](./README.md) | [简体中文](./docs/zh-CN/README.md)
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
|
|
21
|
+
# ⚡ coding-proxy
|
|
22
|
+
|
|
23
|
+
**A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code**
|
|
24
|
+
|
|
25
|
+
[](https://www.python.org/)
|
|
26
|
+
[](#)
|
|
27
|
+
[](https://github.com/astral-sh/uv)
|
|
28
|
+
[](#)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 💡 Why Do We Even Need coding-proxy?
|
|
35
|
+
|
|
36
|
+
When you're deeply immersed in your coding "zone" with **Claude Code** (or any AI assistant relying on Anthropic's Messages API), there's nothing quite as soul-crushing as having your flow violently interrupted by:
|
|
37
|
+
|
|
38
|
+
- 🛑 **Rate Limiting**: High-frequency pings trigger the dreaded `429 rate_limit_error`. Forced to stare at the screen and rethink your life choices.
|
|
39
|
+
- 💸 **Usage Cap**: Aggressive code generation drains your daily/monthly quota, slamming you with a cold, heartless `403` error.
|
|
40
|
+
- 🌋 **Overloaded Servers**: Anthropic's official servers melt down during peak hours, tossing back a merciless `503 overloaded_error`.
|
|
41
|
+
|
|
42
|
+
**coding-proxy** was forged in the developer fires to terminate these exact pain points. Serving as a **purely transparent** intermediate layer, it blesses your Claude Code with millisecond-level "N-tier chained fallback disaster recovery." When your primary vendor goes belly up, it seamlessly and instantly switches your requests to the next smartest available fallback (like GitHub Copilot, Google Antigravity, or even Zhipu GLM)—**with zero manual intervention, and zero perceived interruption.**
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🌟 Core Features
|
|
47
|
+
|
|
48
|
+
- **⛓️ N-tier Chained Failover**: Automatically downgrades from official Claude Plans, gracefully falling back to GitHub Copilot, then Google Antigravity, with Zhipu GLM acting as the ultimate safety net.
|
|
49
|
+
- **🛡️ Smart Resilience & Quota Guardians**: Every single vendor node comes fully armed with an independent **Circuit Breaker** and **Quota Guard** to proactively dodge avalanches without breaking a sweat.
|
|
50
|
+
- **👻 Phantom-like Transparency**: **100% transparent** to the client! No code tweaks required. Overwrite `ANTHROPIC_BASE_URL` with a single line, and you're good to go.
|
|
51
|
+
- **🔄 Universal Alchemy (Formats & Models)**: Native support for two-way request/streaming (SSE) translations between Anthropic ←→ Gemini. Plus, auto/DIY model name mapping (e.g., effortlessly morphing `claude-*` into `glm-*`).
|
|
52
|
+
- **📊 Extreme Observability**: Built-in, zero-BS local monitoring powered by a `SQLite WAL`. The CLI provides a one-click detailed Token usage dashboard (`coding-proxy usage`).
|
|
53
|
+
- **⚡ Featherweight Standalone Deployment**: A fully asynchronous architecture (`FastAPI` + `httpx`). Zero dependency on Redis, message queues, or other heavy machinery—absolutely no extra baggage for your dev rig.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🚀 Quick Start
|
|
58
|
+
|
|
59
|
+
### 1. Prerequisite Checks
|
|
60
|
+
Make sure your rig has **Python 3.13+** and the **`uv`** package manager installed (highly recommended, because life is too short for slow package managers).
|
|
61
|
+
|
|
62
|
+
### 2. Grab the Code & Install
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/ThreeFish-AI/coding-proxy
|
|
65
|
+
cd coding-proxy
|
|
66
|
+
uv sync
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Configure Keys (Using Zhipu GLM as a fallback example)
|
|
70
|
+
```bash
|
|
71
|
+
cp config.example.yaml config.yaml
|
|
72
|
+
# Use environment variables to defensively inject your keys
|
|
73
|
+
export ZHIPU_API_KEY="your-api-key-here"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4. Ignite the Proxy Server
|
|
77
|
+
```bash
|
|
78
|
+
uv run coding-proxy start
|
|
79
|
+
# INFO: Started server process
|
|
80
|
+
# INFO: Uvicorn running on http://127.0.0.1:8046 (Press CTRL+C to quit)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. Seamless Claude Code Integration
|
|
84
|
+
Open a fresh terminal tab, point to the proxy server when firing up Claude Code, and enjoy blissful, uninterrupted coding nirvana:
|
|
85
|
+
```bash
|
|
86
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8046
|
|
87
|
+
claude
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🛠️ The CLI Console Guide
|
|
93
|
+
|
|
94
|
+
`coding-proxy` comes equipped with a badass suite of CLI tools to help you boss around your proxy state.
|
|
95
|
+
|
|
96
|
+
| Command | Description | Example Usage |
|
|
97
|
+
| :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
|
|
98
|
+
| `start` | **Fire up the proxy server.** Supports custom ports and configuration paths. | `coding-proxy start -p 8080 -c ~/config.yaml` |
|
|
99
|
+
| `status` | **Check proxy health.** Shows circuit breaker states (OPEN/CLOSED) and quota status across all tiers. | `coding-proxy status` |
|
|
100
|
+
| `usage` | **Token Stats Dashboard.** Stalks every single token consumed, failovers triggered, and latency across day/vendor/model dimensions. | `coding-proxy usage -d 7 -v anthropic` |
|
|
101
|
+
| `reset` | **The emergency flush button.** Force-reset all circuit breakers and quotas instantly when you've confirmed the main vendor is back from the dead. | `coding-proxy reset` |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📐 Architectural Panorama
|
|
106
|
+
|
|
107
|
+
When a request inevitably hits the fan, the `RequestRouter` slides gracefully down the N-tier tree, juggling circuit breakers and token quotas to decide the ultimate destination:
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
graph TD
|
|
111
|
+
Client["Claude Code Client"]
|
|
112
|
+
Server["FastAPI Server<br/><code>server/app.py</code>"]
|
|
113
|
+
Router["RequestRouter<br/><code>routing/router.py</code>"]
|
|
114
|
+
|
|
115
|
+
Client -->|"POST /v1/messages"| Server
|
|
116
|
+
Server --> Router
|
|
117
|
+
|
|
118
|
+
Router --> T0
|
|
119
|
+
Router --> T1
|
|
120
|
+
Router --> T2
|
|
121
|
+
Router --> TN
|
|
122
|
+
|
|
123
|
+
subgraph T0["Tier 0: Claude Plans"]
|
|
124
|
+
direction LR
|
|
125
|
+
A_VE["AnthropicVendor"]
|
|
126
|
+
A_CB["CB (Circuit Breaker)"]
|
|
127
|
+
A_QG["QG (Quota Guard)"]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
subgraph T1["Tier 1: GitHub Copilot"]
|
|
131
|
+
direction LR
|
|
132
|
+
C_VE["CopilotVendor"]
|
|
133
|
+
C_CB["CB (Circuit Breaker)"]
|
|
134
|
+
C_QG["QG (Quota Guard)"]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
subgraph T2["Tier 2: Google Antigravity"]
|
|
138
|
+
direction LR
|
|
139
|
+
G_VE["AntigravityVendor"]
|
|
140
|
+
G_CB["CB (Circuit Breaker)"]
|
|
141
|
+
G_QG["QG (Quota Guard)"]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
subgraph TN["Tier N: Zhipu (Safety Net)"]
|
|
145
|
+
Z_VE["ZhipuVendor"]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
A_VE --> API_A["Anthropic API"]
|
|
149
|
+
C_VE --> API_C["GitHub Copilot API"]
|
|
150
|
+
G_VE --> API_G["Google Gemini API"]
|
|
151
|
+
Z_VE --> API_Z["Zhipu GLM API"]
|
|
152
|
+
|
|
153
|
+
style T0 fill:#1a5276,color:#fff
|
|
154
|
+
style T1 fill:#1a5276,color:#fff
|
|
155
|
+
style T2 fill:#1a5276,color:#fff
|
|
156
|
+
style TN fill:#7b241c,color:#fff
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
*For a deep dive into the architecture and under-the-hood wizardry, consult [framework.md](./docs/framework.md) (Currently in Chinese).*
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 📚 Detailed Documentation Map
|
|
164
|
+
|
|
165
|
+
To ensure this project outlives us all (long-term maintainability), we offer exhaustive, Evidence-Based documentation:
|
|
166
|
+
|
|
167
|
+
- 📖 **[User Guide](./docs/user-guide.md)** — From installation and bare-minimum configs to the semantic breakdown of every `config.yaml` field and common troubleshooting manuals. (Currently in Chinese)
|
|
168
|
+
- 🏗️ **[Architecture Framework](./docs/framework.md)** — A meticulous decoding of underlying design patterns (Template Method, Circuit Breaker, State Machine, etc.), targeted at devs who want to peek into the matrix or contribute new vendors. (Currently in Chinese)
|
|
169
|
+
- 🤝 **[Engineering Guidelines (AGENTS.md)](./AGENTS.md)** — The systemic context mindset and AI Agent collaboration protocol. It preaches **refactoring, reuse, and orthogonal abstractions** and serves as the ultimate guiding light for all development in this repository.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 💡 Inspiration & Acknowledgements
|
|
174
|
+
|
|
175
|
+
During our chaotic yet rewarding exploration of engineering practices, we were heavily inspired by cutting-edge tech ecosystems and brilliant designs. Special shoutouts:
|
|
176
|
+
|
|
177
|
+
- A massive thank you to **[Claude Code](https://platform.claude.com/docs/en/intro)** for sparking our obsession with crafting the ultimate, seamless programming assistant experience.
|
|
178
|
+
- Endless gratitude to the open-source community's myriad of **API Proxy** projects. Your trailblazing in reverse proxies, high-availability setups (circuit breakers/streaming proxies), and dynamic routing provided the rock-solid theoretical foundation for `coding-proxy`'s elastic N-Tier mechanisms.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
<div align="center">
|
|
183
|
+
<sub>Built with 🧠, ❤️, and an absurd amount of coffee by ThreeFish-AI </sub>
|
|
184
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
coding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
coding/proxy/__init__.py,sha256=PzxvF7x2-CumscBC-FgM0oDgWGGUbFB07gs3sQatDAc,78
|
|
3
|
+
coding/proxy/__main__.py,sha256=5vivXMO3ckMyzrvhTM8u9PpY9nPdMC_-1wFqfoc4xV0,76
|
|
4
|
+
coding/proxy/pricing.py,sha256=iygpqkpn3ZsQLy0XzMqjaNuiwrArI4ytA9xFSQhiTfg,3765
|
|
5
|
+
coding/proxy/auth/__init__.py,sha256=ScXkdxOBPVUxBl82-zXPlX20UWthGdJIC6EOyKYTRaU,469
|
|
6
|
+
coding/proxy/auth/runtime.py,sha256=mDJ8HXyHQNxykeoiLGwROglamX76LKylphlweFGH1fQ,4431
|
|
7
|
+
coding/proxy/auth/store.py,sha256=FCgV4jvfnaMwayv0ilTUWkWJ40n-zpcvgC6AIEFjwuo,2556
|
|
8
|
+
coding/proxy/auth/providers/__init__.py,sha256=hnGL_aSHcMxiZHK6HKPWD0_tWSPcwuxwQtlnAIvf_U8,178
|
|
9
|
+
coding/proxy/auth/providers/base.py,sha256=TOtCi2b81-UBdEoWMv7HbYSmRxAjGSvnF1yBxazqai0,1092
|
|
10
|
+
coding/proxy/auth/providers/github.py,sha256=JPFe5XC45aqKqcqqm_ot9xijH36b8wh9R0rNeTLCn-k,4848
|
|
11
|
+
coding/proxy/auth/providers/google.py,sha256=tdk0B-RmHTSWFT7t3IKKRJ1VoUyFshQyZBdE8GC4OIU,8308
|
|
12
|
+
coding/proxy/cli/__init__.py,sha256=VkvIiOg7cmOl5BM97Ap1gvJTBJ4c5BEU37PZ5J-8gg4,5096
|
|
13
|
+
coding/proxy/cli/auth_commands.py,sha256=1v6xqrgidwsPkIE5iQ-ilClTsvANAUyIbyy8Gq-8PPE,8804
|
|
14
|
+
coding/proxy/compat/__init__.py,sha256=zvpuiFjymMp023ezW6uJ7k1zbz54lBxp8zzjShStbBU,721
|
|
15
|
+
coding/proxy/compat/canonical.py,sha256=47iBGLUjxcf7tee_h3I-lAsltBwuDG0hgAzki9KlWNs,6951
|
|
16
|
+
coding/proxy/compat/session_store.py,sha256=02x0rhPwoJVGqD1hPoKl69v6IEGHArlhIumIydF1rdg,5149
|
|
17
|
+
coding/proxy/config/__init__.py,sha256=hzgU5noJGecjj13UY38cC_p6jpWO3GO7okSW-A2XkJ0,127
|
|
18
|
+
coding/proxy/config/auth_schema.py,sha256=d64djzuFq1OPGRo6eHYHtD9rRpOrQukaSYgNqb1kYhE,706
|
|
19
|
+
coding/proxy/config/loader.py,sha256=ETv4qrYZij__EpXiJggh1BurheDxFhaBFczGjQc94Ww,5133
|
|
20
|
+
coding/proxy/config/resiliency.py,sha256=xSkQyW6rl16gTWcrtvECJLEGA4WNv-ox8Y5NouWAGxU,1191
|
|
21
|
+
coding/proxy/config/routing.py,sha256=PJoyY_8dNWG-yKBdoFhSNpfrSAHk-b7NMC9CH-9GnoE,11010
|
|
22
|
+
coding/proxy/config/schema.py,sha256=gMzigsw0Ue3GRDGh4TNN-Ei7as6SIRYjcFqxhk_o7SE,11783
|
|
23
|
+
coding/proxy/config/server.py,sha256=pWgIxMANBc9uMUz6yU1ixmCQpMRFQFziOOr8mN81Er8,508
|
|
24
|
+
coding/proxy/config/vendors.py,sha256=CtzIKF7-YQ7m0TNKmdRS5T4nqYWneFSUTjCVD1ayYRo,1450
|
|
25
|
+
coding/proxy/convert/__init__.py,sha256=ejoxnBoYJBq9ZO6VmBJOe0FP_EZ9xNfUAY9qfvzvbDU,458
|
|
26
|
+
coding/proxy/convert/anthropic_to_gemini.py,sha256=OFZj3g26j-IoD8x9xvzAIIK8BbD3zo53sieXM06kQ-U,12981
|
|
27
|
+
coding/proxy/convert/anthropic_to_openai.py,sha256=lJaRsMVSgpiJ2hUEi7ORfxBRtm25VC27HJ2yCAGtmdw,12977
|
|
28
|
+
coding/proxy/convert/gemini_sse_adapter.py,sha256=U9oKqUjdmVlFKyzp99vkC5O-NRQtWohxqTVxjhHh_to,6289
|
|
29
|
+
coding/proxy/convert/gemini_to_anthropic.py,sha256=yxIHRT76VzqvIIEZv89WEsoK7doL0hIhg1TGzwv4eUo,3266
|
|
30
|
+
coding/proxy/convert/openai_to_anthropic.py,sha256=63N9YRe8XuMU4NyJyU9kiMfA2bzhGFtdaHVThExmFQc,3434
|
|
31
|
+
coding/proxy/logging/__init__.py,sha256=Xfs6rxNRYtxHEThzAqgw4hX0e_HkkaoKZwXJKn5uGLM,1663
|
|
32
|
+
coding/proxy/logging/db.py,sha256=64BLKEsXeyEKqaRvU5PICkNV90Kj1U-4X1-l0hpQrKE,12504
|
|
33
|
+
coding/proxy/logging/stats.py,sha256=dAqqhjFDTafpeztpaXHY75hgUlmgriPY-25ADWm0QJk,4733
|
|
34
|
+
coding/proxy/model/__init__.py,sha256=JN-88SwtOAn7rV5mD20Du4qchmzj_p2a1MR_G8re5yQ,3864
|
|
35
|
+
coding/proxy/model/auth.py,sha256=NiiIPdLdxPtuHNrsGoxFASUXwJU5pkDuVRorQe_1Z-Q,934
|
|
36
|
+
coding/proxy/model/compat.py,sha256=h7JziBs4IkRrCbJl6rLf2NXotaazcvJdGPlcyHdPQEc,5108
|
|
37
|
+
coding/proxy/model/constants.py,sha256=x2WwgEP-X4EXWLZrzZ1IHW1fQHn-e7QqdEqCftsgJFk,970
|
|
38
|
+
coding/proxy/model/pricing.py,sha256=mtz7hK-o1vzhiKE0Y9C5jlPz666v8yYH8kusXCuItT4,1821
|
|
39
|
+
coding/proxy/model/token.py,sha256=_iEtTX1tEW3X24fCj7IEWmym1SNH1LVvEXTXgh0Eofk,1820
|
|
40
|
+
coding/proxy/model/vendor.py,sha256=8BT1_60qzE470mNxvs8o7lucJpM3FL_8xjH5rG_Tkfk,7813
|
|
41
|
+
coding/proxy/routing/__init__.py,sha256=NhyvsZFxKAS-dxu2aB9140QEIlTumnKQktsdF4kGZ2o,1570
|
|
42
|
+
coding/proxy/routing/circuit_breaker.py,sha256=JHCjp465ja-E57pN3YZSB72q-ZGR4FFcx8QYFogX6kc,5979
|
|
43
|
+
coding/proxy/routing/error_classifier.py,sha256=16nbjbvOc9ngc9vv04QC-CucSNT89VVQ5LNBZsfO4Nw,1937
|
|
44
|
+
coding/proxy/routing/executor.py,sha256=-WModyQwGdLdamrrpNsMJFfVAIna6KcXtiDQcdfRTLE,20123
|
|
45
|
+
coding/proxy/routing/model_mapper.py,sha256=bocbZy1bhI1U-SMhc-a2ZSDo3EnjXB35f5sKfdK9QQI,3224
|
|
46
|
+
coding/proxy/routing/quota_guard.py,sha256=hp0_X_RAdP1b1qP5gwaOLgBiJbMloObcl6LRhKckTyI,6424
|
|
47
|
+
coding/proxy/routing/rate_limit.py,sha256=VKk8qsfRo9Rn7fWg-ofNCo79DoOn_dfxHm1Y-ISMcAM,5154
|
|
48
|
+
coding/proxy/routing/retry.py,sha256=pOPnR7uduWr-_tQQw7iQwp-ZxHgsLYA4v0XW1034E98,2417
|
|
49
|
+
coding/proxy/routing/router.py,sha256=mbao6pTDS23swuKz6XJIIZjAzmq2tuPluqiNOgIAUNM,2998
|
|
50
|
+
coding/proxy/routing/session_manager.py,sha256=HfdFVEMvpciIm9lZ3dlAskZ2OZfgV7O9rx-tLkQey0Q,2616
|
|
51
|
+
coding/proxy/routing/tier.py,sha256=PgGdJGRpth2cxyLuJI6_gWV6aAgRr4F_H8k6PV3KrfE,6770
|
|
52
|
+
coding/proxy/routing/usage_parser.py,sha256=MDKxpid7GXME8CJwzOxPJn1I1vUK6p3BLMvZIfSacX0,7562
|
|
53
|
+
coding/proxy/routing/usage_recorder.py,sha256=udcLJAR2CaMPVH_ApC7onkT4GnrCerrMQY14eW9_xIU,5676
|
|
54
|
+
coding/proxy/server/__init__.py,sha256=KeH7mEu36v9v27m3VgeSxSeFz9sLTJrxS_EVATJ7Vks,20
|
|
55
|
+
coding/proxy/server/app.py,sha256=SI6-1AMC_vUr-UHf_Kbs3P9MQpd6sDPU-wUmaLGf-lY,5424
|
|
56
|
+
coding/proxy/server/factory.py,sha256=HmHbr6uhwqfL-uEFDLqQKaWrvw0V91V0RdJfDk93c7Y,6854
|
|
57
|
+
coding/proxy/server/request_normalizer.py,sha256=EPjoFZIo0f7i2XJWU-4qjLuukZmoDXeFA-qkDLP3pLU,5379
|
|
58
|
+
coding/proxy/server/responses.py,sha256=gGy5ULvVLtpTFQ2o-N_Omblz4wVRa0LhNEiOQTQEu3k,2161
|
|
59
|
+
coding/proxy/server/routes.py,sha256=xoLLP8tSqLJN-AZalINa8iOKqGgRiWIjA3LrrmYpBxA,11080
|
|
60
|
+
coding/proxy/streaming/__init__.py,sha256=0al5TC-zJt8Bz0CibTmd8WPza-Cs-qcuZWvT2fKPqHI,23
|
|
61
|
+
coding/proxy/streaming/anthropic_compat.py,sha256=maPkQRfEENUUyDMKbaatC_YzkNUhKZYf4owzGgoxdlE,19136
|
|
62
|
+
coding/proxy/vendors/__init__.py,sha256=0WSueYMg6lFW12OTVNLEv7JpUoLqufrQjA7PDYQBLxQ,971
|
|
63
|
+
coding/proxy/vendors/anthropic.py,sha256=jbCG4NAQxsfX8K6elakIvxTdsFEXmC31GqOvcCPcR-g,1514
|
|
64
|
+
coding/proxy/vendors/antigravity.py,sha256=KhnkGaqiTITBb3XfzA6976fhfvue5qtTfiJ9NJsMKuA,13046
|
|
65
|
+
coding/proxy/vendors/base.py,sha256=9RZ-Bb6ZNDXVf5YZ8IJ9xu2XvtFF73JTq5kEfbVxsc8,14281
|
|
66
|
+
coding/proxy/vendors/copilot.py,sha256=zfKEIDS6jcjROt8EThcda_ziO69a3DXfbcg6YlHgius,28615
|
|
67
|
+
coding/proxy/vendors/copilot_models.py,sha256=EZMTyIMkyN8lkHg9CmLF02B6pMwnajk5flJRCVowERU,15492
|
|
68
|
+
coding/proxy/vendors/copilot_token_manager.py,sha256=Ps0oAsoTfsaLohYr50y8pW4XXDEXNlTn-v9cWf-vfZk,6560
|
|
69
|
+
coding/proxy/vendors/copilot_urls.py,sha256=FEFmmbR9v4GDfTupb41g2jUqZ3Xd-tvOc1DA2ZPNLRc,403
|
|
70
|
+
coding/proxy/vendors/mixins.py,sha256=eZd_vlsPCKUNOEWSxWTDDb5i24GUIE3_JZDsj5LMr08,2583
|
|
71
|
+
coding/proxy/vendors/token_manager.py,sha256=Ru8alZ1hSIuvHvSXS9dZyz01kabH18ZBo8Jd0pIuOeg,4127
|
|
72
|
+
coding/proxy/vendors/zhipu.py,sha256=QoPqHQRX-Y7pAyTSgWGKMnmQJc7KejoF074fRjWYRUA,9399
|
|
73
|
+
coding_proxy-0.1.0.dist-info/METADATA,sha256=z4hPhD82rUFiH2wOFnYmhdIYvXycoSaKIh1UPAytYIs,9234
|
|
74
|
+
coding_proxy-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
75
|
+
coding_proxy-0.1.0.dist-info/entry_points.txt,sha256=moIVzt5ho0Wk9B47LOo2SEAbhzuDDHWi-EfM30U0XBg,54
|
|
76
|
+
coding_proxy-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
77
|
+
coding_proxy-0.1.0.dist-info/RECORD,,
|