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.
Files changed (77) hide show
  1. coding/__init__.py +0 -0
  2. coding/proxy/__init__.py +3 -0
  3. coding/proxy/__main__.py +5 -0
  4. coding/proxy/auth/__init__.py +13 -0
  5. coding/proxy/auth/providers/__init__.py +6 -0
  6. coding/proxy/auth/providers/base.py +35 -0
  7. coding/proxy/auth/providers/github.py +133 -0
  8. coding/proxy/auth/providers/google.py +237 -0
  9. coding/proxy/auth/runtime.py +122 -0
  10. coding/proxy/auth/store.py +74 -0
  11. coding/proxy/cli/__init__.py +151 -0
  12. coding/proxy/cli/auth_commands.py +224 -0
  13. coding/proxy/compat/__init__.py +30 -0
  14. coding/proxy/compat/canonical.py +193 -0
  15. coding/proxy/compat/session_store.py +137 -0
  16. coding/proxy/config/__init__.py +6 -0
  17. coding/proxy/config/auth_schema.py +24 -0
  18. coding/proxy/config/loader.py +139 -0
  19. coding/proxy/config/resiliency.py +46 -0
  20. coding/proxy/config/routing.py +279 -0
  21. coding/proxy/config/schema.py +280 -0
  22. coding/proxy/config/server.py +23 -0
  23. coding/proxy/config/vendors.py +53 -0
  24. coding/proxy/convert/__init__.py +14 -0
  25. coding/proxy/convert/anthropic_to_gemini.py +352 -0
  26. coding/proxy/convert/anthropic_to_openai.py +352 -0
  27. coding/proxy/convert/gemini_sse_adapter.py +169 -0
  28. coding/proxy/convert/gemini_to_anthropic.py +98 -0
  29. coding/proxy/convert/openai_to_anthropic.py +88 -0
  30. coding/proxy/logging/__init__.py +49 -0
  31. coding/proxy/logging/db.py +308 -0
  32. coding/proxy/logging/stats.py +129 -0
  33. coding/proxy/model/__init__.py +93 -0
  34. coding/proxy/model/auth.py +32 -0
  35. coding/proxy/model/compat.py +153 -0
  36. coding/proxy/model/constants.py +21 -0
  37. coding/proxy/model/pricing.py +70 -0
  38. coding/proxy/model/token.py +64 -0
  39. coding/proxy/model/vendor.py +218 -0
  40. coding/proxy/pricing.py +100 -0
  41. coding/proxy/routing/__init__.py +47 -0
  42. coding/proxy/routing/circuit_breaker.py +152 -0
  43. coding/proxy/routing/error_classifier.py +67 -0
  44. coding/proxy/routing/executor.py +453 -0
  45. coding/proxy/routing/model_mapper.py +90 -0
  46. coding/proxy/routing/quota_guard.py +169 -0
  47. coding/proxy/routing/rate_limit.py +159 -0
  48. coding/proxy/routing/retry.py +82 -0
  49. coding/proxy/routing/router.py +84 -0
  50. coding/proxy/routing/session_manager.py +62 -0
  51. coding/proxy/routing/tier.py +171 -0
  52. coding/proxy/routing/usage_parser.py +193 -0
  53. coding/proxy/routing/usage_recorder.py +131 -0
  54. coding/proxy/server/__init__.py +1 -0
  55. coding/proxy/server/app.py +142 -0
  56. coding/proxy/server/factory.py +175 -0
  57. coding/proxy/server/request_normalizer.py +139 -0
  58. coding/proxy/server/responses.py +74 -0
  59. coding/proxy/server/routes.py +264 -0
  60. coding/proxy/streaming/__init__.py +1 -0
  61. coding/proxy/streaming/anthropic_compat.py +484 -0
  62. coding/proxy/vendors/__init__.py +29 -0
  63. coding/proxy/vendors/anthropic.py +44 -0
  64. coding/proxy/vendors/antigravity.py +328 -0
  65. coding/proxy/vendors/base.py +353 -0
  66. coding/proxy/vendors/copilot.py +702 -0
  67. coding/proxy/vendors/copilot_models.py +438 -0
  68. coding/proxy/vendors/copilot_token_manager.py +167 -0
  69. coding/proxy/vendors/copilot_urls.py +16 -0
  70. coding/proxy/vendors/mixins.py +71 -0
  71. coding/proxy/vendors/token_manager.py +128 -0
  72. coding/proxy/vendors/zhipu.py +243 -0
  73. coding_proxy-0.1.0.dist-info/METADATA +184 -0
  74. coding_proxy-0.1.0.dist-info/RECORD +77 -0
  75. coding_proxy-0.1.0.dist-info/WHEEL +4 -0
  76. coding_proxy-0.1.0.dist-info/entry_points.txt +2 -0
  77. 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
+ [![Python Version](https://img.shields.io/badge/python-3.13%2B-blue?style=flat-square&logo=python)](https://www.python.org/)
26
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](#)
27
+ [![Package Manager](https://img.shields.io/badge/pkg-uv-purple?style=flat-square)](https://github.com/astral-sh/uv)
28
+ [![Architecture](https://img.shields.io/badge/architecture-N--tier-orange?style=flat-square)](#)
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ coding-proxy = coding.proxy.cli:app