coding-proxy 0.1.0a1__tar.gz → 0.1.0a5__tar.gz

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 (140) hide show
  1. coding_proxy-0.1.0a5/.github/workflows/ci.yml +137 -0
  2. coding_proxy-0.1.0a5/.github/workflows/coverage.yml +89 -0
  3. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/.gitignore +7 -0
  4. coding_proxy-0.1.0a5/CHANGELOG.md +34 -0
  5. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/PKG-INFO +4 -4
  6. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/README.md +1 -1
  7. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/docs/user-guide.md +8 -8
  8. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/docs/zh-CN/README.md +1 -1
  9. coding_proxy-0.1.0a5/pyproject.toml +89 -0
  10. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/__init__.py +1 -0
  11. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/__init__.py +7 -3
  12. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/providers/github.py +1 -3
  13. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/providers/google.py +24 -18
  14. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/runtime.py +1 -1
  15. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/store.py +0 -1
  16. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/cli/__init__.py +28 -13
  17. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/cli/auth_commands.py +49 -21
  18. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/compat/canonical.py +69 -46
  19. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/compat/session_store.py +10 -4
  20. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/auth_schema.py +2 -2
  21. coding_proxy-0.1.0a1/config.example.yaml → coding_proxy-0.1.0a5/src/coding/proxy/config/config.default.yaml +14 -4
  22. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/loader.py +74 -22
  23. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/resiliency.py +5 -1
  24. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/routing.py +58 -25
  25. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/schema.py +72 -42
  26. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/server.py +1 -0
  27. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/vendors.py +6 -2
  28. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/convert/anthropic_to_gemini.py +28 -14
  29. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/convert/anthropic_to_openai.py +78 -43
  30. coding_proxy-0.1.0a5/src/coding/proxy/convert/gemini_sse_adapter.py +219 -0
  31. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/convert/gemini_to_anthropic.py +26 -16
  32. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/convert/openai_to_anthropic.py +25 -11
  33. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/logging/__init__.py +1 -0
  34. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/logging/db.py +61 -28
  35. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/logging/stats.py +11 -9
  36. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/__init__.py +46 -26
  37. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/compat.py +6 -5
  38. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/constants.py +15 -6
  39. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/pricing.py +2 -2
  40. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/token.py +1 -1
  41. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/vendor.py +27 -10
  42. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/__init__.py +20 -9
  43. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/circuit_breaker.py +9 -6
  44. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/error_classifier.py +3 -1
  45. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/executor.py +180 -43
  46. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/model_mapper.py +17 -5
  47. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/quota_guard.py +11 -3
  48. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/rate_limit.py +4 -4
  49. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/retry.py +6 -6
  50. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/router.py +3 -2
  51. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/session_manager.py +23 -10
  52. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/tier.py +16 -5
  53. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/usage_parser.py +90 -50
  54. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/routing/usage_recorder.py +59 -24
  55. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/app.py +27 -11
  56. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/factory.py +30 -14
  57. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/request_normalizer.py +7 -3
  58. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/responses.py +3 -1
  59. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/routes.py +67 -18
  60. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/streaming/anthropic_compat.py +195 -97
  61. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/__init__.py +24 -14
  62. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/anthropic.py +6 -2
  63. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/antigravity.py +26 -10
  64. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/base.py +62 -26
  65. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/copilot.py +94 -30
  66. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/copilot_models.py +19 -12
  67. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/copilot_token_manager.py +21 -7
  68. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/copilot_urls.py +1 -2
  69. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/token_manager.py +7 -3
  70. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/zhipu.py +29 -9
  71. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_antigravity.py +62 -41
  72. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_app_routes.py +189 -119
  73. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_auto_login.py +91 -45
  74. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_cli_usage.py +5 -3
  75. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_compat.py +15 -3
  76. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_config_loader.py +99 -27
  77. coding_proxy-0.1.0a5/tests/test_convert_request.py +457 -0
  78. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_convert_response.py +120 -87
  79. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_convert_sse.py +209 -102
  80. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_copilot.py +256 -107
  81. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_copilot_convert_request.py +150 -95
  82. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_copilot_convert_response.py +124 -76
  83. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_copilot_models.py +25 -9
  84. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_copilot_urls.py +26 -7
  85. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_currency.py +2 -3
  86. coding_proxy-0.1.0a5/tests/test_error_classifier.py +226 -0
  87. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_compat.py +42 -14
  88. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_constants.py +8 -9
  89. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_mapper.py +40 -18
  90. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_pricing.py +3 -1
  91. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_token.py +7 -2
  92. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_vendor.py +110 -51
  93. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_parse_usage.py +109 -64
  94. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_pricing.py +41 -20
  95. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_quota_guard.py +1 -1
  96. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_rate_limit.py +3 -4
  97. coding_proxy-0.1.0a5/tests/test_request_normalizer.py +91 -0
  98. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_router_chain.py +409 -214
  99. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_router_executor.py +143 -68
  100. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_runtime_reauth.py +11 -6
  101. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_schema.py +50 -18
  102. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_streaming_anthropic_compat.py +158 -77
  103. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_tier.py +50 -10
  104. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_tiers_config.py +19 -33
  105. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_token_logger.py +104 -53
  106. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_token_manager.py +4 -2
  107. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_types.py +55 -26
  108. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_vendor_streaming.py +60 -23
  109. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_vendors.py +136 -72
  110. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_zhipu.py +49 -15
  111. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/uv.lock +128 -1
  112. coding_proxy-0.1.0a1/CHANGELOG.md +0 -38
  113. coding_proxy-0.1.0a1/pyproject.toml +0 -48
  114. coding_proxy-0.1.0a1/src/coding/proxy/convert/gemini_sse_adapter.py +0 -169
  115. coding_proxy-0.1.0a1/tests/test_convert_request.py +0 -319
  116. coding_proxy-0.1.0a1/tests/test_error_classifier.py +0 -176
  117. coding_proxy-0.1.0a1/tests/test_request_normalizer.py +0 -82
  118. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/.github/workflows/promote.yml +0 -0
  119. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/.github/workflows/release.yml +0 -0
  120. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/AGENTS.md +0 -0
  121. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/CLAUDE.md +0 -0
  122. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/LICENSE +0 -0
  123. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/docs/ci-cd.md +0 -0
  124. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/docs/framework.md +0 -0
  125. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/__init__.py +0 -0
  126. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/__main__.py +0 -0
  127. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/providers/__init__.py +0 -0
  128. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/auth/providers/base.py +0 -0
  129. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/compat/__init__.py +1 -1
  130. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/config/__init__.py +0 -0
  131. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/convert/__init__.py +1 -1
  132. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/model/auth.py +0 -0
  133. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/pricing.py +0 -0
  134. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/server/__init__.py +0 -0
  135. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/streaming/__init__.py +0 -0
  136. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/src/coding/proxy/vendors/mixins.py +0 -0
  137. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/__init__.py +0 -0
  138. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_circuit_breaker.py +0 -0
  139. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_mixins.py +0 -0
  140. {coding_proxy-0.1.0a1 → coding_proxy-0.1.0a5}/tests/test_model_auth.py +0 -0
@@ -0,0 +1,137 @@
1
+ # =============================================================================
2
+ # coding-proxy: Continuous Integration Pipeline
3
+ # =============================================================================
4
+ # Trigger:
5
+ # - Pull Request (所有 PR)
6
+ # - Push to master / feature/* 分支
7
+ # - Push of version tags (v*) — 确保 release 前通过质量检查
8
+ #
9
+ # Architecture: Parallel Quality Gates
10
+ # Job 1 (lint): Ruff 静态分析
11
+ # Job 2 (format): Ruff format 格式校验
12
+ # Job 3 (test): pytest 单元/集成测试
13
+ # → 三者完全并行,最大化效率
14
+ #
15
+ # References:
16
+ # [1] https://docs.astral.sh/ruff/integrations/#github-actions
17
+ # [2] https://docs.pytest.org/en/stable/how-to/continuous_integration.html
18
+ # =============================================================================
19
+
20
+ name: CI
21
+
22
+ on:
23
+ pull_request:
24
+ branches: [master, "feature/*"]
25
+ push:
26
+ branches: [master, "feature/*"]
27
+ tags: ["v*"]
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ env:
33
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
34
+
35
+ concurrency:
36
+ group: ${{ github.workflow }}-${{ github.ref }}
37
+ cancel-in-progress: true
38
+
39
+ jobs:
40
+ # ===========================================================================
41
+ # Job 1: LINT — Ruff static analysis
42
+ # ===========================================================================
43
+ lint:
44
+ name: Lint (Ruff)
45
+ runs-on: ubuntu-latest
46
+ timeout-minutes: 5
47
+
48
+ steps:
49
+ - name: Checkout repository
50
+ uses: actions/checkout@v4
51
+ with:
52
+ persist-credentials: false
53
+
54
+ - name: Set up uv
55
+ uses: astral-sh/setup-uv@v4
56
+ with:
57
+ enable-cache: true
58
+
59
+ - name: Install dependencies
60
+ run: uv sync --all-extras --dev
61
+
62
+ - name: Run Ruff linter
63
+ run: uv run ruff check .
64
+
65
+ # ===========================================================================
66
+ # Job 2: FORMAT — Ruff format verification
67
+ # ===========================================================================
68
+ format:
69
+ name: Format Check (Ruff)
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 5
72
+
73
+ steps:
74
+ - name: Checkout repository
75
+ uses: actions/checkout@v4
76
+ with:
77
+ persist-credentials: false
78
+
79
+ - name: Set up uv
80
+ uses: astral-sh/setup-uv@v4
81
+ with:
82
+ enable-cache: true
83
+
84
+ - name: Install dependencies
85
+ run: uv sync --all-extras --dev
86
+
87
+ - name: Check formatting
88
+ run: uv run ruff format --check .
89
+
90
+ # ===========================================================================
91
+ # Job 3: TEST — Unit & integration tests
92
+ # ===========================================================================
93
+ test:
94
+ name: Test (pytest)
95
+ runs-on: ubuntu-latest
96
+ timeout-minutes: 10
97
+ strategy:
98
+ matrix:
99
+ python-version: ["3.12", "3.13"]
100
+
101
+ steps:
102
+ - name: Checkout repository
103
+ uses: actions/checkout@v4
104
+ with:
105
+ persist-credentials: false
106
+
107
+ - name: Set up Python ${{ matrix.python-version }}
108
+ uses: actions/setup-python@v5
109
+ with:
110
+ python-version: ${{ matrix.python-version }}
111
+
112
+ - name: Set up uv
113
+ uses: astral-sh/setup-uv@v4
114
+ with:
115
+ enable-cache: true
116
+
117
+ - name: Install dependencies
118
+ run: uv sync --all-extras --dev
119
+
120
+ - name: Run tests with coverage data collection
121
+ run: uv run pytest --cov=src/coding --cov-report=term-missing --cov-report=xml:coverage.xml --junitxml=junit.xml
122
+
123
+ - name: Upload coverage artifact
124
+ if: matrix.python-version == '3.12'
125
+ uses: actions/upload-artifact@v4
126
+ with:
127
+ name: coverage-data
128
+ path: coverage.xml
129
+ retention-days: 7
130
+
131
+ - name: Upload test results
132
+ if: always()
133
+ uses: actions/upload-artifact@v4
134
+ with:
135
+ name: test-results-py${{ matrix.python-version }}
136
+ path: junit.xml
137
+ retention-days: 7
@@ -0,0 +1,89 @@
1
+ # =============================================================================
2
+ # coding-proxy: Test Coverage Reporting
3
+ # =============================================================================
4
+ # Trigger:
5
+ # - Pull Request (生成覆盖率报告)
6
+ # - Push to master (主分支更新时记录基线)
7
+ #
8
+ # Purpose:
9
+ # - 生成 HTML 覆盖率报告供下载查看
10
+ # - 在 Actions Summary 中展示覆盖率摘要
11
+ # - 设置最低覆盖率门槛(初始为 0,后续可逐步提高)
12
+ #
13
+ # References:
14
+ # [1] https://pytest-cov.readthedocs.io/
15
+ # [2] https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/adding-a-job-summary
16
+ # =============================================================================
17
+
18
+ name: Coverage
19
+
20
+ on:
21
+ pull_request:
22
+ branches: [master, "feature/*"]
23
+ push:
24
+ branches: [master]
25
+
26
+ permissions:
27
+ contents: read
28
+
29
+ env:
30
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
31
+
32
+ concurrency:
33
+ group: ${{ github.workflow }}-${{ github.ref }}
34
+ cancel-in-progress: true
35
+
36
+ jobs:
37
+ coverage:
38
+ name: Coverage Report
39
+ runs-on: ubuntu-latest
40
+ timeout-minutes: 10
41
+
42
+ steps:
43
+ - name: Checkout repository
44
+ uses: actions/checkout@v4
45
+ with:
46
+ persist-credentials: false
47
+
48
+ - name: Set up Python
49
+ uses: actions/setup-python@v5
50
+ with:
51
+ python-version: "3.12"
52
+
53
+ - name: Set up uv
54
+ uses: astral-sh/setup-uv@v4
55
+ with:
56
+ enable-cache: true
57
+
58
+ - name: Install dependencies
59
+ run: uv sync --all-extras --dev
60
+
61
+ - name: Generate coverage report
62
+ run: |
63
+ uv run pytest \
64
+ --cov=src/coding \
65
+ --cov-report=term-missing \
66
+ --cov-report=html:htmlcov \
67
+ --cov-report=xml:coverage.xml \
68
+ --cov-fail-under=0 \
69
+ --junitxml=junit.xml
70
+
71
+ - name: Upload HTML coverage report
72
+ uses: actions/upload-artifact@v4
73
+ with:
74
+ name: coverage-html-report
75
+ path: htmlcov/
76
+ retention-days: 14
77
+
78
+ - name: Upload coverage XML report
79
+ uses: actions/upload-artifact@v4
80
+ with:
81
+ name: coverage-xml
82
+ path: coverage.xml
83
+ retention-days: 14
84
+
85
+ - name: Display coverage summary in job summary
86
+ run: |
87
+ echo "## 📊 测试覆盖率摘要" >> $GITHUB_STEP_SUMMARY
88
+ echo "" >> $GITHUB_STEP_SUMMARY
89
+ uv run pytest --cov=src/coding --cov-report=term-missing --no-header -q || true
@@ -10,8 +10,15 @@ build/
10
10
  *.whl
11
11
  .uv/
12
12
 
13
+ # Coverage reports (pytest-cov)
14
+ .coverage
15
+ htmlcov/
16
+ coverage.xml
17
+ junit.xml
18
+
13
19
  # Custom
14
20
  .idea/
15
21
  .temp/
16
22
  config.yaml
17
23
  .claude/.prompts.md
24
+ .python-version
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ 本文件基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/) 规范维护,版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [v0.1.0](https://github.com/ThreeFish-AI/coding-proxy/releases/tag/v0.1.0) — 2026-04-05
8
+
9
+ > [!IMPORTANT]
10
+ >
11
+ > **🎉 coding-proxy MVP 惊艳登场!**
12
+ >
13
+ > 仅需配置一行环境变量,立刻为你的 Claude Code 接入“永不宕机”的多源智能引擎。主供应商打盹?毫秒级自动无缝切换备用通道,全天候护航你的编码心流,向打断大声说不!
14
+
15
+ ### ✨ 核心亮点
16
+
17
+ - **N-tier 高可用接力**:随心编排供应商优先级;默认内置 `Claude → GitHub Copilot → Antigravity → GLM` 丝滑降级链路,天塌下来有 Proxy 顶着;
18
+ - **自愈式智能熔断**:微秒级状态机严防“雪崩效应”,搭配指数退避重试,一旦主干回血,静默自愈切回;
19
+ - **账单刺客克星**:极客专属的 SQLite 本地账本 + CLI 多维看板(按维度:日/模型/供应商),把 Token 消耗拆解到每一比特,精打细算不背锅;
20
+ - **OAuth2 丝滑接入**:原生集成 GitHub Device Flow 与 Google OAuth。告别干枯的断更密钥,令牌到期自动接力轮转,专注写码不分心;
21
+ - **多协议“同传专家”**:Anthropic 与 OpenAI / Gemini 协议底层双向无损翻译,鸡同鸭讲?在 proxy 层是不存在的;
22
+ - **模型指名道姓**:随需定制你的神级转发地图,`claude-opus-*` 秒变 `glm-5v-turbo`,指哪打哪,模型矩阵全由你做主;
23
+ - **全透明“隐身衣”**:FastAPI 强劲异步驱动,开箱即用。仅需覆盖注入 `ANTHROPIC_BASE_URL`,对上层应用百分百零侵入、零违和;
24
+ - **SSE 星际流水线**:彻底打破协议壁垒,流式连线跨体系无损透传,体验每一颗 Token 如丝般顺滑的输出快感;
25
+ - **双擎配额守卫**:“5小时滑动窗口 + 固化周配额”双重护城河。余额濒临红线?主动预警机制,断然拒绝突然“断奶”;
26
+
27
+ ### 🔧 更多特性
28
+
29
+ - 💰 **细粒度计价引擎**:内置主流大模型实时公开保价,调用开销追踪精确至每分每厘,资本家也薅不到你一根毛;
30
+ - 🔄 **强迫症级重试流**:深度可配的指数退避策略(不仅是次数,还有倍率),将偶发性异常全部静默拦截在黑盒之中;
31
+ - 🧠 **Vendor 降级脑图**:内置多维度供应商能力全息映射,危机时刻全自动施行“损失最小”的兼容降级路线;
32
+ - ⏱️ **RateLimit 算命仪**:智能嗅探并解析 Rate Limit Headers,精准算准每一秒 CD 冷却,弹无虚发;
33
+ - 🛡️ **神秘 421 疫苗**:专治 GitHub Copilot 偶尔抽风的著名 `421 Misdirection` 顽疾,内置“即刻重试”自愈特效药;
34
+ - 🧹 **洁癖级优雅退出**:挥一挥衣袖不带走一片云彩,挂起、清理、落数据,进程结束得干干净净,像风一样自由;
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-proxy
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a5
4
4
  Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM.
5
5
  Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
6
- Project-URL: docs, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
7
- Project-URL: bug-tracker, https://github.com/ThreeFish-AI/coding-proxy/issues
6
+ Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
7
+ Project-URL: Bug Tracker, https://github.com/ThreeFish-AI/coding-proxy/issues
8
8
  Author-email: ThreeFish-AI <threefish.ai@gmail.com>
9
9
  License-Expression: Apache-2.0
10
10
  License-File: LICENSE
@@ -79,7 +79,7 @@ uv sync
79
79
 
80
80
  ### 3. Configure Keys (Using Zhipu GLM as a fallback example)
81
81
  ```bash
82
- cp config.example.yaml config.yaml
82
+ cp config.default.yaml config.yaml
83
83
  # Use environment variables to defensively inject your keys
84
84
  export ZHIPU_API_KEY="your-api-key-here"
85
85
  ```
@@ -52,7 +52,7 @@ uv sync
52
52
 
53
53
  ### 3. Configure Keys (Using Zhipu GLM as a fallback example)
54
54
  ```bash
55
- cp config.example.yaml config.yaml
55
+ cp config.default.yaml config.yaml
56
56
  # Use environment variables to defensively inject your keys
57
57
  export ZHIPU_API_KEY="your-api-key-here"
58
58
  ```
@@ -147,7 +147,7 @@ pip install -e .
147
147
 
148
148
  ```bash
149
149
  # 复制配置模板到项目根目录(模板已内置完整默认值,仅需覆盖密钥)
150
- cp config.example.yaml config.yaml
150
+ cp config.default.yaml config.yaml
151
151
  ```
152
152
 
153
153
  设置智谱 API Key(二选一):
@@ -240,7 +240,7 @@ export ANTHROPIC_BASE_URL=http://127.0.0.1:8046
240
240
  3. `~/.coding-proxy/config.yaml`(用户主目录)
241
241
  4. 内置默认值(无需配置文件也可启动)
242
242
 
243
- 加载器会以 `config.example.yaml` 为基础模板进行深度合并,用户配置中的字段覆盖模板默认值。
243
+ 加载器会以 `config.default.yaml` 为基础模板进行深度合并,用户配置中的字段覆盖模板默认值。
244
244
 
245
245
  ### 3.2 vendors — 供应商定义
246
246
 
@@ -522,7 +522,7 @@ flowchart TD
522
522
 
523
523
  **匹配优先级**:同一供应商内精确匹配 > 正则匹配(按规则顺序) > 供应商默认值
524
524
 
525
- **典型配置**(基于 `config.example.yaml`):
525
+ **典型配置**(基于 `config.default.yaml`):
526
526
 
527
527
  ```yaml
528
528
  model_mapping:
@@ -678,10 +678,10 @@ auth:
678
678
  如果配置文件中使用上述旧格式字段(而非 `vendors` 列表),系统会在启动时**自动迁移**至 vendors 格式并输出日志提示:
679
679
 
680
680
  ```
681
- 检测到旧 flat 格式配置字段,已自动迁移至 vendors 列表格式。建议迁移至 config.example.yaml 中的 vendors 新格式。
681
+ 检测到旧 flat 格式配置字段,已自动迁移至 vendors 列表格式。建议迁移至 config.default.yaml 中的 vendors 新格式。
682
682
  ```
683
683
 
684
- **建议**:尽快迁移至 `config.example.yaml` 中的 vendors 新格式,以获得最完整的配置能力(如 `weekly_quota_guard`、`retry`、`pricing` 等新功能仅在 vendors 格式中可用)。
684
+ **建议**:尽快迁移至 `config.default.yaml` 中的 vendors 新格式,以获得最完整的配置能力(如 `weekly_quota_guard`、`retry`、`pricing` 等新功能仅在 vendors 格式中可用)。
685
685
 
686
686
  > **迁移时间线**:旧 flat 格式自动迁移代码将在后续版本中移除。建议在新部署中直接使用 vendors 格式,避免依赖迁移逻辑。
687
687
 
@@ -1398,7 +1398,7 @@ coding-proxy auth reauth github
1398
1398
 
1399
1399
  ## 附录 B:完整配置参考
1400
1400
 
1401
- > **说明**:本附录为 [`config.example.yaml`](../config.example.yaml) 的精简摘要。完整注释版(含每行注释说明)请直接参阅项目根目录下的 `config.example.yaml`,它是唯一权威配置模板。
1401
+ > **说明**:本附录为 [`config.default.yaml`](../config.default.yaml) 的精简摘要。完整注释版(含每行注释说明)请直接参阅项目根目录下的 `config.default.yaml`,它是唯一权威配置模板。
1402
1402
  >
1403
1403
  > 以下省略的字段在代码中均有合理默认值(参见各配置表格中的「默认值」列),无需显式配置即可生效。
1404
1404
 
@@ -1460,7 +1460,7 @@ failover:
1460
1460
  # 注:FailoverConfig 代码默认值额外包含 "limit exceeded" 和 "capacity"
1461
1461
 
1462
1462
  model_mapping:
1463
- # 完整列表参见 config.example.yaml 及 §3.5
1463
+ # 完整列表参见 config.default.yaml 及 §3.5
1464
1464
  - pattern: "claude-sonnet-.*"
1465
1465
  vendors: ["copilot"]
1466
1466
  target: "claude-sonnet-4.6"
@@ -1468,7 +1468,7 @@ model_mapping:
1468
1468
  # ... 更多映射规则(按供应商分组) ...
1469
1469
 
1470
1470
  pricing: # 可选:费用统计(四维定价 $/¥)
1471
- # 完整列表参见 config.example.yaml(~20 条,覆盖全部供应商与模型)
1471
+ # 完整列表参见 config.default.yaml(~20 条,覆盖全部供应商与模型)
1472
1472
  - vendor: anthropic
1473
1473
  model: claude-sonnet-4-6
1474
1474
  input_cost_per_mtok: $3.0
@@ -52,7 +52,7 @@ uv sync
52
52
 
53
53
  ### 3. 配置密钥 (以智谱 GLM 兜底为例)
54
54
  ```bash
55
- cp config.example.yaml config.yaml
55
+ cp config.default.yaml config.yaml
56
56
  # 使用环境变量注入保护你的密钥
57
57
  export ZHIPU_API_KEY="your-api-key-here"
58
58
  ```
@@ -0,0 +1,89 @@
1
+ [project]
2
+ name = "coding-proxy"
3
+ version = "0.1.0a5"
4
+ description = "A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ authors = [{ name = "ThreeFish-AI", email = "threefish.ai@gmail.com" }]
8
+ license = "Apache-2.0"
9
+ keywords = ["claude-code", "vibe-coding", "llm-agent", "antigravity", "github-copilot", "glm"]
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ ]
17
+
18
+ dependencies = [
19
+ "fastapi>=0.135",
20
+ "uvicorn>=0.42",
21
+ "httpx>=0.28",
22
+ "pydantic>=2.12",
23
+ "pyyaml>=6.0",
24
+ "typer>=0.12.5,<0.22.0",
25
+ "aiosqlite>=0.22",
26
+ "rich>=14.3",
27
+ ]
28
+
29
+ [project.urls]
30
+ "Source Code" = "https://github.com/ThreeFish-AI/coding-proxy"
31
+ "User Guide" = "https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md"
32
+ "Bug Tracker" = "https://github.com/ThreeFish-AI/coding-proxy/issues"
33
+
34
+ [project.scripts]
35
+ coding-proxy = "coding.proxy.cli:app"
36
+
37
+ [build-system]
38
+ requires = ["hatchling>=1.29"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/coding"]
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "pytest>=9.0",
47
+ "pytest-asyncio>=1.3",
48
+ "pytest-cov>=6.0",
49
+ "ruff>=0.9.0",
50
+ ]
51
+
52
+ # ── Ruff: Linter & Formatter(替代 flake8 + isort + Black + pyupgrade 等 6+ 工具) ──
53
+ # 文档: https://docs.astral.sh/ruff/
54
+ [tool.ruff]
55
+ target-version = "py312"
56
+ line-length = 88
57
+
58
+ [tool.ruff.lint]
59
+ select = [
60
+ "F", # Pyflakes(未定义变量、导入错误等 — 捕获真实 Bug)
61
+ "E", # pycodestyle 错误
62
+ "W", # pycodestyle 警告
63
+ "I", # isort(导入排序)
64
+ "UP", # pyupgrade(Python 语法现代化,安全自动修复)
65
+ ]
66
+ ignore = [
67
+ "E501", # 行过长(由 formatter 处理)
68
+ "E402", # 模块级非顶层导入(__init__.py 文档化分组等合理场景)
69
+ ]
70
+
71
+ [tool.ruff.lint.isort]
72
+ known-first-party = ["coding"]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ "__init__.py" = ["F401"] # __init__.py 允许未使用的导入(re-export)
76
+
77
+ [tool.ruff.format]
78
+ quote-style = "double"
79
+ indent-style = "space"
80
+ docstring-code-format = true
81
+
82
+ # ── Pytest 配置 ──
83
+ [tool.pytest.ini_options]
84
+ asyncio_mode = "auto"
85
+ testpaths = ["tests"]
86
+ addopts = "-v --tb=short"
87
+ filterwarnings = [
88
+ "ignore::DeprecationWarning",
89
+ ]
@@ -12,6 +12,7 @@ def __get_version() -> str:
12
12
  """
13
13
  try:
14
14
  from importlib.metadata import version as _meta_version
15
+
15
16
  return _meta_version("coding-proxy")
16
17
  except Exception:
17
18
  pass
@@ -7,7 +7,11 @@ from .runtime import ReauthState, RuntimeReauthCoordinator
7
7
  from .store import ProviderTokens, TokenStoreManager
8
8
 
9
9
  __all__ = [
10
- "OAuthProvider", "GitHubDeviceFlowProvider", "GoogleOAuthProvider",
11
- "RuntimeReauthCoordinator", "ReauthState",
12
- "ProviderTokens", "TokenStoreManager",
10
+ "OAuthProvider",
11
+ "GitHubDeviceFlowProvider",
12
+ "GoogleOAuthProvider",
13
+ "RuntimeReauthCoordinator",
14
+ "ReauthState",
15
+ "ProviderTokens",
16
+ "TokenStoreManager",
13
17
  ]
@@ -4,10 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import logging
7
- import time
8
- from typing import Any
9
-
10
7
  import webbrowser
8
+ from typing import Any
11
9
 
12
10
  import httpx
13
11
 
@@ -6,7 +6,7 @@ import asyncio
6
6
  import logging
7
7
  import secrets
8
8
  import time
9
- from http.server import HTTPServer, BaseHTTPRequestHandler
9
+ from http.server import BaseHTTPRequestHandler, HTTPServer
10
10
  from typing import Any
11
11
  from urllib.parse import parse_qs, urlencode, urlparse
12
12
 
@@ -21,8 +21,7 @@ logger = logging.getLogger(__name__)
21
21
  # SOT(权威源): coding.proxy.config.schema.AuthConfig
22
22
  # 此处默认值仅作 fallback,生产环境应通过 config.yaml 的 auth 段覆盖
23
23
  _DEFAULT_CLIENT_ID = (
24
- "1071006060591-tmhssin2h21lcre235vtolojh4g403ep"
25
- ".apps.googleusercontent.com"
24
+ "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"
26
25
  )
27
26
  _DEFAULT_CLIENT_SECRET = "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"
28
27
 
@@ -44,7 +43,9 @@ class _CallbackHandler(BaseHTTPRequestHandler):
44
43
  使用实例级 result dict 避免类属性在并发场景下的交叉污染.
45
44
  """
46
45
 
47
- def __init__(self, *args: Any, result: dict[str, str | None], **kwargs: Any) -> None:
46
+ def __init__(
47
+ self, *args: Any, result: dict[str, str | None], **kwargs: Any
48
+ ) -> None:
48
49
  self._result = result
49
50
  super().__init__(*args, **kwargs)
50
51
 
@@ -103,7 +104,11 @@ class GoogleOAuthProvider(OAuthProvider):
103
104
  async def login(self) -> ProviderTokens:
104
105
  """执行 Google OAuth2 Code Flow,返回 Token."""
105
106
  state = secrets.token_urlsafe(32)
106
- result: dict[str, str | None] = {"auth_code": None, "state": None, "error": None}
107
+ result: dict[str, str | None] = {
108
+ "auth_code": None,
109
+ "state": None,
110
+ "error": None,
111
+ }
107
112
 
108
113
  def _make_handler(*args: Any, **kwargs: Any) -> _CallbackHandler:
109
114
  return _CallbackHandler(*args, result=result, **kwargs)
@@ -113,23 +118,26 @@ class GoogleOAuthProvider(OAuthProvider):
113
118
  redirect_port = server.server_address[1]
114
119
  redirect_uri = f"http://127.0.0.1:{redirect_port}/callback"
115
120
 
116
- params = urlencode({
117
- "client_id": self._client_id,
118
- "redirect_uri": redirect_uri,
119
- "response_type": "code",
120
- "scope": " ".join(_SCOPES),
121
- "state": state,
122
- "access_type": "offline",
123
- "prompt": "consent",
124
- })
121
+ params = urlencode(
122
+ {
123
+ "client_id": self._client_id,
124
+ "redirect_uri": redirect_uri,
125
+ "response_type": "code",
126
+ "scope": " ".join(_SCOPES),
127
+ "state": state,
128
+ "access_type": "offline",
129
+ "prompt": "consent",
130
+ }
131
+ )
125
132
  auth_url = f"{_AUTH_URL}?{params}"
126
133
 
127
134
  logger.info("请在浏览器中完成 Google 授权")
128
- print(f"\n 🔗 请在浏览器中访问以下链接完成授权:\n")
135
+ print("\n 🔗 请在浏览器中访问以下链接完成授权:\n")
129
136
  print(f" {auth_url}\n")
130
137
 
131
138
  # 打开浏览器
132
139
  import webbrowser
140
+
133
141
  webbrowser.open(auth_url)
134
142
 
135
143
  # 等待回调
@@ -153,9 +161,7 @@ class GoogleOAuthProvider(OAuthProvider):
153
161
  # 交换 code → token
154
162
  return await self._exchange_code(result["auth_code"], redirect_uri)
155
163
 
156
- async def _exchange_code(
157
- self, code: str, redirect_uri: str
158
- ) -> ProviderTokens:
164
+ async def _exchange_code(self, code: str, redirect_uri: str) -> ProviderTokens:
159
165
  """将 authorization code 交换为 access_token + refresh_token."""
160
166
  resp = await self._http.post(
161
167
  _TOKEN_URL,
@@ -6,7 +6,7 @@ import asyncio
6
6
  import enum
7
7
  import logging
8
8
  import time
9
- from typing import Callable
9
+ from collections.abc import Callable
10
10
 
11
11
  from .providers.base import OAuthProvider
12
12
  from .store import TokenStoreManager
@@ -11,7 +11,6 @@ from __future__ import annotations
11
11
 
12
12
  import json
13
13
  import logging
14
- import time
15
14
  from pathlib import Path
16
15
  from typing import Any
17
16