ccproxy-api 0.1.6__py3-none-any.whl → 0.2.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 (481) hide show
  1. ccproxy/api/__init__.py +1 -15
  2. ccproxy/api/app.py +439 -212
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +145 -176
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +402 -530
  9. ccproxy/api/middleware/hooks.py +563 -0
  10. ccproxy/api/middleware/normalize_headers.py +59 -0
  11. ccproxy/api/middleware/request_id.py +35 -16
  12. ccproxy/api/middleware/streaming_hooks.py +292 -0
  13. ccproxy/api/routes/__init__.py +5 -14
  14. ccproxy/api/routes/health.py +39 -672
  15. ccproxy/api/routes/plugins.py +277 -0
  16. ccproxy/auth/__init__.py +2 -19
  17. ccproxy/auth/bearer.py +25 -15
  18. ccproxy/auth/dependencies.py +123 -157
  19. ccproxy/auth/exceptions.py +0 -12
  20. ccproxy/auth/manager.py +35 -49
  21. ccproxy/auth/managers/__init__.py +10 -0
  22. ccproxy/auth/managers/base.py +523 -0
  23. ccproxy/auth/managers/base_enhanced.py +63 -0
  24. ccproxy/auth/managers/token_snapshot.py +77 -0
  25. ccproxy/auth/models/base.py +65 -0
  26. ccproxy/auth/models/credentials.py +40 -0
  27. ccproxy/auth/oauth/__init__.py +4 -18
  28. ccproxy/auth/oauth/base.py +533 -0
  29. ccproxy/auth/oauth/cli_errors.py +37 -0
  30. ccproxy/auth/oauth/flows.py +430 -0
  31. ccproxy/auth/oauth/protocol.py +366 -0
  32. ccproxy/auth/oauth/registry.py +408 -0
  33. ccproxy/auth/oauth/router.py +396 -0
  34. ccproxy/auth/oauth/routes.py +186 -113
  35. ccproxy/auth/oauth/session.py +151 -0
  36. ccproxy/auth/oauth/templates.py +342 -0
  37. ccproxy/auth/storage/__init__.py +2 -5
  38. ccproxy/auth/storage/base.py +279 -5
  39. ccproxy/auth/storage/generic.py +134 -0
  40. ccproxy/cli/__init__.py +1 -2
  41. ccproxy/cli/_settings_help.py +351 -0
  42. ccproxy/cli/commands/auth.py +1519 -793
  43. ccproxy/cli/commands/config/commands.py +209 -276
  44. ccproxy/cli/commands/plugins.py +669 -0
  45. ccproxy/cli/commands/serve.py +75 -810
  46. ccproxy/cli/commands/status.py +254 -0
  47. ccproxy/cli/decorators.py +83 -0
  48. ccproxy/cli/helpers.py +22 -60
  49. ccproxy/cli/main.py +359 -10
  50. ccproxy/cli/options/claude_options.py +0 -25
  51. ccproxy/config/__init__.py +7 -11
  52. ccproxy/config/core.py +227 -0
  53. ccproxy/config/env_generator.py +232 -0
  54. ccproxy/config/runtime.py +67 -0
  55. ccproxy/config/security.py +36 -3
  56. ccproxy/config/settings.py +382 -441
  57. ccproxy/config/toml_generator.py +299 -0
  58. ccproxy/config/utils.py +452 -0
  59. ccproxy/core/__init__.py +7 -271
  60. ccproxy/{_version.py → core/_version.py} +16 -3
  61. ccproxy/core/async_task_manager.py +516 -0
  62. ccproxy/core/async_utils.py +47 -14
  63. ccproxy/core/auth/__init__.py +6 -0
  64. ccproxy/core/constants.py +16 -50
  65. ccproxy/core/errors.py +53 -0
  66. ccproxy/core/id_utils.py +20 -0
  67. ccproxy/core/interfaces.py +16 -123
  68. ccproxy/core/logging.py +473 -18
  69. ccproxy/core/plugins/__init__.py +77 -0
  70. ccproxy/core/plugins/cli_discovery.py +211 -0
  71. ccproxy/core/plugins/declaration.py +455 -0
  72. ccproxy/core/plugins/discovery.py +604 -0
  73. ccproxy/core/plugins/factories.py +967 -0
  74. ccproxy/core/plugins/hooks/__init__.py +30 -0
  75. ccproxy/core/plugins/hooks/base.py +58 -0
  76. ccproxy/core/plugins/hooks/events.py +46 -0
  77. ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
  78. ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
  79. ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
  80. ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
  81. ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
  82. ccproxy/core/plugins/hooks/layers.py +44 -0
  83. ccproxy/core/plugins/hooks/manager.py +186 -0
  84. ccproxy/core/plugins/hooks/registry.py +139 -0
  85. ccproxy/core/plugins/hooks/thread_manager.py +203 -0
  86. ccproxy/core/plugins/hooks/types.py +22 -0
  87. ccproxy/core/plugins/interfaces.py +416 -0
  88. ccproxy/core/plugins/loader.py +166 -0
  89. ccproxy/core/plugins/middleware.py +233 -0
  90. ccproxy/core/plugins/models.py +59 -0
  91. ccproxy/core/plugins/protocol.py +180 -0
  92. ccproxy/core/plugins/runtime.py +519 -0
  93. ccproxy/{observability/context.py → core/request_context.py} +137 -94
  94. ccproxy/core/status_report.py +211 -0
  95. ccproxy/core/transformers.py +13 -8
  96. ccproxy/data/claude_headers_fallback.json +558 -0
  97. ccproxy/data/codex_headers_fallback.json +121 -0
  98. ccproxy/http/__init__.py +30 -0
  99. ccproxy/http/base.py +95 -0
  100. ccproxy/http/client.py +323 -0
  101. ccproxy/http/hooks.py +642 -0
  102. ccproxy/http/pool.py +279 -0
  103. ccproxy/llms/formatters/__init__.py +7 -0
  104. ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
  105. ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
  106. ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
  107. ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
  108. ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
  109. ccproxy/llms/formatters/base.py +140 -0
  110. ccproxy/llms/formatters/base_model.py +33 -0
  111. ccproxy/llms/formatters/common/__init__.py +51 -0
  112. ccproxy/llms/formatters/common/identifiers.py +48 -0
  113. ccproxy/llms/formatters/common/streams.py +254 -0
  114. ccproxy/llms/formatters/common/thinking.py +74 -0
  115. ccproxy/llms/formatters/common/usage.py +135 -0
  116. ccproxy/llms/formatters/constants.py +55 -0
  117. ccproxy/llms/formatters/context.py +116 -0
  118. ccproxy/llms/formatters/mapping.py +33 -0
  119. ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
  120. ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
  121. ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
  122. ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
  123. ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
  124. ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
  125. ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
  126. ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
  127. ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
  128. ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
  129. ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
  130. ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
  131. ccproxy/llms/formatters/utils.py +306 -0
  132. ccproxy/llms/models/__init__.py +9 -0
  133. ccproxy/llms/models/anthropic.py +619 -0
  134. ccproxy/llms/models/openai.py +844 -0
  135. ccproxy/llms/streaming/__init__.py +26 -0
  136. ccproxy/llms/streaming/accumulators.py +1074 -0
  137. ccproxy/llms/streaming/formatters.py +251 -0
  138. ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
  139. ccproxy/models/__init__.py +8 -159
  140. ccproxy/models/detection.py +92 -193
  141. ccproxy/models/provider.py +75 -0
  142. ccproxy/plugins/access_log/README.md +32 -0
  143. ccproxy/plugins/access_log/__init__.py +20 -0
  144. ccproxy/plugins/access_log/config.py +33 -0
  145. ccproxy/plugins/access_log/formatter.py +126 -0
  146. ccproxy/plugins/access_log/hook.py +763 -0
  147. ccproxy/plugins/access_log/logger.py +254 -0
  148. ccproxy/plugins/access_log/plugin.py +137 -0
  149. ccproxy/plugins/access_log/writer.py +109 -0
  150. ccproxy/plugins/analytics/README.md +24 -0
  151. ccproxy/plugins/analytics/__init__.py +1 -0
  152. ccproxy/plugins/analytics/config.py +5 -0
  153. ccproxy/plugins/analytics/ingest.py +85 -0
  154. ccproxy/plugins/analytics/models.py +97 -0
  155. ccproxy/plugins/analytics/plugin.py +121 -0
  156. ccproxy/plugins/analytics/routes.py +163 -0
  157. ccproxy/plugins/analytics/service.py +284 -0
  158. ccproxy/plugins/claude_api/README.md +29 -0
  159. ccproxy/plugins/claude_api/__init__.py +10 -0
  160. ccproxy/plugins/claude_api/adapter.py +829 -0
  161. ccproxy/plugins/claude_api/config.py +52 -0
  162. ccproxy/plugins/claude_api/detection_service.py +461 -0
  163. ccproxy/plugins/claude_api/health.py +175 -0
  164. ccproxy/plugins/claude_api/hooks.py +284 -0
  165. ccproxy/plugins/claude_api/models.py +256 -0
  166. ccproxy/plugins/claude_api/plugin.py +298 -0
  167. ccproxy/plugins/claude_api/routes.py +118 -0
  168. ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
  169. ccproxy/plugins/claude_api/tasks.py +84 -0
  170. ccproxy/plugins/claude_sdk/README.md +35 -0
  171. ccproxy/plugins/claude_sdk/__init__.py +80 -0
  172. ccproxy/plugins/claude_sdk/adapter.py +749 -0
  173. ccproxy/plugins/claude_sdk/auth.py +57 -0
  174. ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
  175. ccproxy/plugins/claude_sdk/config.py +210 -0
  176. ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
  177. ccproxy/plugins/claude_sdk/detection_service.py +163 -0
  178. ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
  179. ccproxy/plugins/claude_sdk/health.py +113 -0
  180. ccproxy/plugins/claude_sdk/hooks.py +115 -0
  181. ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
  182. ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
  183. ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
  184. ccproxy/plugins/claude_sdk/options.py +154 -0
  185. ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
  186. ccproxy/plugins/claude_sdk/plugin.py +269 -0
  187. ccproxy/plugins/claude_sdk/routes.py +104 -0
  188. ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
  189. ccproxy/plugins/claude_sdk/session_pool.py +700 -0
  190. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
  191. ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
  192. ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
  193. ccproxy/plugins/claude_sdk/tasks.py +97 -0
  194. ccproxy/plugins/claude_shared/README.md +18 -0
  195. ccproxy/plugins/claude_shared/__init__.py +12 -0
  196. ccproxy/plugins/claude_shared/model_defaults.py +171 -0
  197. ccproxy/plugins/codex/README.md +35 -0
  198. ccproxy/plugins/codex/__init__.py +6 -0
  199. ccproxy/plugins/codex/adapter.py +635 -0
  200. ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
  201. ccproxy/plugins/codex/detection_service.py +544 -0
  202. ccproxy/plugins/codex/health.py +162 -0
  203. ccproxy/plugins/codex/hooks.py +263 -0
  204. ccproxy/plugins/codex/model_defaults.py +39 -0
  205. ccproxy/plugins/codex/models.py +263 -0
  206. ccproxy/plugins/codex/plugin.py +275 -0
  207. ccproxy/plugins/codex/routes.py +129 -0
  208. ccproxy/plugins/codex/streaming_metrics.py +324 -0
  209. ccproxy/plugins/codex/tasks.py +106 -0
  210. ccproxy/plugins/codex/utils/__init__.py +1 -0
  211. ccproxy/plugins/codex/utils/sse_parser.py +106 -0
  212. ccproxy/plugins/command_replay/README.md +34 -0
  213. ccproxy/plugins/command_replay/__init__.py +17 -0
  214. ccproxy/plugins/command_replay/config.py +133 -0
  215. ccproxy/plugins/command_replay/formatter.py +432 -0
  216. ccproxy/plugins/command_replay/hook.py +294 -0
  217. ccproxy/plugins/command_replay/plugin.py +161 -0
  218. ccproxy/plugins/copilot/README.md +39 -0
  219. ccproxy/plugins/copilot/__init__.py +11 -0
  220. ccproxy/plugins/copilot/adapter.py +465 -0
  221. ccproxy/plugins/copilot/config.py +155 -0
  222. ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
  223. ccproxy/plugins/copilot/detection_service.py +255 -0
  224. ccproxy/plugins/copilot/manager.py +275 -0
  225. ccproxy/plugins/copilot/model_defaults.py +284 -0
  226. ccproxy/plugins/copilot/models.py +148 -0
  227. ccproxy/plugins/copilot/oauth/__init__.py +16 -0
  228. ccproxy/plugins/copilot/oauth/client.py +494 -0
  229. ccproxy/plugins/copilot/oauth/models.py +385 -0
  230. ccproxy/plugins/copilot/oauth/provider.py +602 -0
  231. ccproxy/plugins/copilot/oauth/storage.py +170 -0
  232. ccproxy/plugins/copilot/plugin.py +360 -0
  233. ccproxy/plugins/copilot/routes.py +294 -0
  234. ccproxy/plugins/credential_balancer/README.md +124 -0
  235. ccproxy/plugins/credential_balancer/__init__.py +6 -0
  236. ccproxy/plugins/credential_balancer/config.py +270 -0
  237. ccproxy/plugins/credential_balancer/factory.py +415 -0
  238. ccproxy/plugins/credential_balancer/hook.py +51 -0
  239. ccproxy/plugins/credential_balancer/manager.py +587 -0
  240. ccproxy/plugins/credential_balancer/plugin.py +146 -0
  241. ccproxy/plugins/dashboard/README.md +25 -0
  242. ccproxy/plugins/dashboard/__init__.py +1 -0
  243. ccproxy/plugins/dashboard/config.py +8 -0
  244. ccproxy/plugins/dashboard/plugin.py +71 -0
  245. ccproxy/plugins/dashboard/routes.py +67 -0
  246. ccproxy/plugins/docker/README.md +32 -0
  247. ccproxy/{docker → plugins/docker}/__init__.py +3 -0
  248. ccproxy/{docker → plugins/docker}/adapter.py +108 -10
  249. ccproxy/plugins/docker/config.py +82 -0
  250. ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
  251. ccproxy/{docker → plugins/docker}/middleware.py +2 -2
  252. ccproxy/plugins/docker/plugin.py +198 -0
  253. ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
  254. ccproxy/plugins/duckdb_storage/README.md +26 -0
  255. ccproxy/plugins/duckdb_storage/__init__.py +1 -0
  256. ccproxy/plugins/duckdb_storage/config.py +22 -0
  257. ccproxy/plugins/duckdb_storage/plugin.py +128 -0
  258. ccproxy/plugins/duckdb_storage/routes.py +51 -0
  259. ccproxy/plugins/duckdb_storage/storage.py +633 -0
  260. ccproxy/plugins/max_tokens/README.md +38 -0
  261. ccproxy/plugins/max_tokens/__init__.py +12 -0
  262. ccproxy/plugins/max_tokens/adapter.py +235 -0
  263. ccproxy/plugins/max_tokens/config.py +86 -0
  264. ccproxy/plugins/max_tokens/models.py +53 -0
  265. ccproxy/plugins/max_tokens/plugin.py +200 -0
  266. ccproxy/plugins/max_tokens/service.py +271 -0
  267. ccproxy/plugins/max_tokens/token_limits.json +54 -0
  268. ccproxy/plugins/metrics/README.md +35 -0
  269. ccproxy/plugins/metrics/__init__.py +10 -0
  270. ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
  271. ccproxy/plugins/metrics/config.py +85 -0
  272. ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
  273. ccproxy/plugins/metrics/hook.py +403 -0
  274. ccproxy/plugins/metrics/plugin.py +268 -0
  275. ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
  276. ccproxy/plugins/metrics/routes.py +107 -0
  277. ccproxy/plugins/metrics/tasks.py +117 -0
  278. ccproxy/plugins/oauth_claude/README.md +35 -0
  279. ccproxy/plugins/oauth_claude/__init__.py +14 -0
  280. ccproxy/plugins/oauth_claude/client.py +270 -0
  281. ccproxy/plugins/oauth_claude/config.py +84 -0
  282. ccproxy/plugins/oauth_claude/manager.py +482 -0
  283. ccproxy/plugins/oauth_claude/models.py +266 -0
  284. ccproxy/plugins/oauth_claude/plugin.py +149 -0
  285. ccproxy/plugins/oauth_claude/provider.py +571 -0
  286. ccproxy/plugins/oauth_claude/storage.py +212 -0
  287. ccproxy/plugins/oauth_codex/README.md +38 -0
  288. ccproxy/plugins/oauth_codex/__init__.py +14 -0
  289. ccproxy/plugins/oauth_codex/client.py +224 -0
  290. ccproxy/plugins/oauth_codex/config.py +95 -0
  291. ccproxy/plugins/oauth_codex/manager.py +256 -0
  292. ccproxy/plugins/oauth_codex/models.py +239 -0
  293. ccproxy/plugins/oauth_codex/plugin.py +146 -0
  294. ccproxy/plugins/oauth_codex/provider.py +574 -0
  295. ccproxy/plugins/oauth_codex/storage.py +92 -0
  296. ccproxy/plugins/permissions/README.md +28 -0
  297. ccproxy/plugins/permissions/__init__.py +22 -0
  298. ccproxy/plugins/permissions/config.py +28 -0
  299. ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
  300. ccproxy/plugins/permissions/handlers/protocol.py +33 -0
  301. ccproxy/plugins/permissions/handlers/terminal.py +675 -0
  302. ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
  303. ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
  304. ccproxy/plugins/permissions/plugin.py +153 -0
  305. ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
  306. ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
  307. ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
  308. ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
  309. ccproxy/plugins/pricing/README.md +34 -0
  310. ccproxy/plugins/pricing/__init__.py +6 -0
  311. ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
  312. ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
  313. ccproxy/plugins/pricing/exceptions.py +35 -0
  314. ccproxy/plugins/pricing/loader.py +440 -0
  315. ccproxy/{pricing → plugins/pricing}/models.py +13 -23
  316. ccproxy/plugins/pricing/plugin.py +169 -0
  317. ccproxy/plugins/pricing/service.py +191 -0
  318. ccproxy/plugins/pricing/tasks.py +300 -0
  319. ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
  320. ccproxy/plugins/pricing/utils.py +99 -0
  321. ccproxy/plugins/request_tracer/README.md +40 -0
  322. ccproxy/plugins/request_tracer/__init__.py +7 -0
  323. ccproxy/plugins/request_tracer/config.py +120 -0
  324. ccproxy/plugins/request_tracer/hook.py +415 -0
  325. ccproxy/plugins/request_tracer/plugin.py +255 -0
  326. ccproxy/scheduler/__init__.py +2 -14
  327. ccproxy/scheduler/core.py +26 -41
  328. ccproxy/scheduler/manager.py +63 -107
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +346 -314
  331. ccproxy/services/__init__.py +0 -1
  332. ccproxy/services/adapters/__init__.py +11 -0
  333. ccproxy/services/adapters/base.py +123 -0
  334. ccproxy/services/adapters/chain_composer.py +88 -0
  335. ccproxy/services/adapters/chain_validation.py +44 -0
  336. ccproxy/services/adapters/chat_accumulator.py +200 -0
  337. ccproxy/services/adapters/delta_utils.py +142 -0
  338. ccproxy/services/adapters/format_adapter.py +136 -0
  339. ccproxy/services/adapters/format_context.py +11 -0
  340. ccproxy/services/adapters/format_registry.py +158 -0
  341. ccproxy/services/adapters/http_adapter.py +1045 -0
  342. ccproxy/services/adapters/mock_adapter.py +118 -0
  343. ccproxy/services/adapters/protocols.py +35 -0
  344. ccproxy/services/adapters/simple_converters.py +571 -0
  345. ccproxy/services/auth_registry.py +180 -0
  346. ccproxy/services/cache/__init__.py +6 -0
  347. ccproxy/services/cache/response_cache.py +261 -0
  348. ccproxy/services/cli_detection.py +437 -0
  349. ccproxy/services/config/__init__.py +6 -0
  350. ccproxy/services/config/proxy_configuration.py +111 -0
  351. ccproxy/services/container.py +256 -0
  352. ccproxy/services/factories.py +380 -0
  353. ccproxy/services/handler_config.py +76 -0
  354. ccproxy/services/interfaces.py +298 -0
  355. ccproxy/services/mocking/__init__.py +6 -0
  356. ccproxy/services/mocking/mock_handler.py +291 -0
  357. ccproxy/services/tracing/__init__.py +7 -0
  358. ccproxy/services/tracing/interfaces.py +61 -0
  359. ccproxy/services/tracing/null_tracer.py +57 -0
  360. ccproxy/streaming/__init__.py +23 -0
  361. ccproxy/streaming/buffer.py +1056 -0
  362. ccproxy/streaming/deferred.py +897 -0
  363. ccproxy/streaming/handler.py +117 -0
  364. ccproxy/streaming/interfaces.py +77 -0
  365. ccproxy/streaming/simple_adapter.py +39 -0
  366. ccproxy/streaming/sse.py +109 -0
  367. ccproxy/streaming/sse_parser.py +127 -0
  368. ccproxy/templates/__init__.py +6 -0
  369. ccproxy/templates/plugin_scaffold.py +695 -0
  370. ccproxy/testing/endpoints/__init__.py +33 -0
  371. ccproxy/testing/endpoints/cli.py +215 -0
  372. ccproxy/testing/endpoints/config.py +874 -0
  373. ccproxy/testing/endpoints/console.py +57 -0
  374. ccproxy/testing/endpoints/models.py +100 -0
  375. ccproxy/testing/endpoints/runner.py +1903 -0
  376. ccproxy/testing/endpoints/tools.py +308 -0
  377. ccproxy/testing/mock_responses.py +70 -1
  378. ccproxy/testing/response_handlers.py +20 -0
  379. ccproxy/utils/__init__.py +0 -6
  380. ccproxy/utils/binary_resolver.py +476 -0
  381. ccproxy/utils/caching.py +327 -0
  382. ccproxy/utils/cli_logging.py +101 -0
  383. ccproxy/utils/command_line.py +251 -0
  384. ccproxy/utils/headers.py +228 -0
  385. ccproxy/utils/model_mapper.py +120 -0
  386. ccproxy/utils/startup_helpers.py +95 -342
  387. ccproxy/utils/version_checker.py +279 -6
  388. ccproxy_api-0.2.0.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
  392. ccproxy/__init__.py +0 -4
  393. ccproxy/adapters/__init__.py +0 -11
  394. ccproxy/adapters/base.py +0 -80
  395. ccproxy/adapters/codex/__init__.py +0 -11
  396. ccproxy/adapters/openai/__init__.py +0 -42
  397. ccproxy/adapters/openai/adapter.py +0 -953
  398. ccproxy/adapters/openai/models.py +0 -412
  399. ccproxy/adapters/openai/response_adapter.py +0 -355
  400. ccproxy/adapters/openai/response_models.py +0 -178
  401. ccproxy/api/middleware/headers.py +0 -49
  402. ccproxy/api/middleware/logging.py +0 -180
  403. ccproxy/api/middleware/request_content_logging.py +0 -297
  404. ccproxy/api/middleware/server_header.py +0 -58
  405. ccproxy/api/responses.py +0 -89
  406. ccproxy/api/routes/claude.py +0 -371
  407. ccproxy/api/routes/codex.py +0 -1231
  408. ccproxy/api/routes/metrics.py +0 -1029
  409. ccproxy/api/routes/proxy.py +0 -211
  410. ccproxy/api/services/__init__.py +0 -6
  411. ccproxy/auth/conditional.py +0 -84
  412. ccproxy/auth/credentials_adapter.py +0 -93
  413. ccproxy/auth/models.py +0 -118
  414. ccproxy/auth/oauth/models.py +0 -48
  415. ccproxy/auth/openai/__init__.py +0 -13
  416. ccproxy/auth/openai/credentials.py +0 -166
  417. ccproxy/auth/openai/oauth_client.py +0 -334
  418. ccproxy/auth/openai/storage.py +0 -184
  419. ccproxy/auth/storage/json_file.py +0 -158
  420. ccproxy/auth/storage/keyring.py +0 -189
  421. ccproxy/claude_sdk/__init__.py +0 -18
  422. ccproxy/claude_sdk/options.py +0 -194
  423. ccproxy/claude_sdk/session_pool.py +0 -550
  424. ccproxy/cli/docker/__init__.py +0 -34
  425. ccproxy/cli/docker/adapter_factory.py +0 -157
  426. ccproxy/cli/docker/params.py +0 -274
  427. ccproxy/config/auth.py +0 -153
  428. ccproxy/config/claude.py +0 -348
  429. ccproxy/config/cors.py +0 -79
  430. ccproxy/config/discovery.py +0 -95
  431. ccproxy/config/docker_settings.py +0 -264
  432. ccproxy/config/observability.py +0 -158
  433. ccproxy/config/reverse_proxy.py +0 -31
  434. ccproxy/config/scheduler.py +0 -108
  435. ccproxy/config/server.py +0 -86
  436. ccproxy/config/validators.py +0 -231
  437. ccproxy/core/codex_transformers.py +0 -389
  438. ccproxy/core/http.py +0 -328
  439. ccproxy/core/http_transformers.py +0 -812
  440. ccproxy/core/proxy.py +0 -143
  441. ccproxy/core/validators.py +0 -288
  442. ccproxy/models/errors.py +0 -42
  443. ccproxy/models/messages.py +0 -269
  444. ccproxy/models/requests.py +0 -107
  445. ccproxy/models/responses.py +0 -270
  446. ccproxy/models/types.py +0 -102
  447. ccproxy/observability/__init__.py +0 -51
  448. ccproxy/observability/access_logger.py +0 -457
  449. ccproxy/observability/sse_events.py +0 -303
  450. ccproxy/observability/stats_printer.py +0 -753
  451. ccproxy/observability/storage/__init__.py +0 -1
  452. ccproxy/observability/storage/duckdb_simple.py +0 -677
  453. ccproxy/observability/storage/models.py +0 -70
  454. ccproxy/observability/streaming_response.py +0 -107
  455. ccproxy/pricing/__init__.py +0 -19
  456. ccproxy/pricing/loader.py +0 -251
  457. ccproxy/services/claude_detection_service.py +0 -269
  458. ccproxy/services/codex_detection_service.py +0 -263
  459. ccproxy/services/credentials/__init__.py +0 -55
  460. ccproxy/services/credentials/config.py +0 -105
  461. ccproxy/services/credentials/manager.py +0 -561
  462. ccproxy/services/credentials/oauth_client.py +0 -481
  463. ccproxy/services/proxy_service.py +0 -1827
  464. ccproxy/static/.keep +0 -0
  465. ccproxy/utils/cost_calculator.py +0 -210
  466. ccproxy/utils/disconnection_monitor.py +0 -83
  467. ccproxy/utils/model_mapping.py +0 -199
  468. ccproxy/utils/models_provider.py +0 -150
  469. ccproxy/utils/simple_request_logger.py +0 -284
  470. ccproxy/utils/streaming_metrics.py +0 -199
  471. ccproxy_api-0.1.6.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.6.dist-info/RECORD +0 -189
  473. ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
  474. /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
  475. /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
  476. /ccproxy/{docker → plugins/docker}/models.py +0 -0
  477. /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
  478. /ccproxy/{docker → plugins/docker}/validators.py +0 -0
  479. /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
  480. /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
  481. {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,385 @@
1
+ """GitHub Copilot-specific authentication models."""
2
+
3
+ from datetime import UTC, datetime
4
+ from typing import Any, Literal
5
+
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ SecretStr,
11
+ computed_field,
12
+ field_serializer,
13
+ field_validator,
14
+ )
15
+
16
+ from ccproxy.auth.models.base import BaseProfileInfo, BaseTokenInfo
17
+
18
+
19
+ class CopilotOAuthToken(BaseModel):
20
+ """OAuth token information for GitHub Copilot."""
21
+
22
+ model_config = ConfigDict(
23
+ populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
24
+ )
25
+
26
+ access_token: SecretStr = Field(..., alias="access_token")
27
+ token_type: str = Field(default="bearer", alias="token_type")
28
+ expires_in: int | None = Field(default=None, alias="expires_in")
29
+ refresh_token: SecretStr | None = Field(default=None, alias="refresh_token")
30
+ scope: str = Field(default="read:user", alias="scope")
31
+ created_at: int | None = Field(default=None, alias="created_at")
32
+
33
+ @field_serializer("access_token", "refresh_token")
34
+ def serialize_secret(self, value: SecretStr | None) -> str | None:
35
+ """Serialize SecretStr to plain string for JSON output."""
36
+ return value.get_secret_value() if value else None
37
+
38
+ @field_validator("access_token", "refresh_token", mode="before")
39
+ @classmethod
40
+ def validate_tokens(cls, v: str | SecretStr | None) -> SecretStr | None:
41
+ """Convert string values to SecretStr."""
42
+ if v is None:
43
+ return None
44
+ if isinstance(v, str):
45
+ return SecretStr(v)
46
+ return v
47
+
48
+ def __repr__(self) -> str:
49
+ """Safe string representation that masks sensitive tokens."""
50
+ access_token_str = self.access_token.get_secret_value()
51
+ access_preview = (
52
+ f"{access_token_str[:8]}...{access_token_str[-8:]}"
53
+ if len(access_token_str) > 16
54
+ else "***"
55
+ )
56
+
57
+ refresh_preview = "***"
58
+ if self.refresh_token:
59
+ refresh_token_str = self.refresh_token.get_secret_value()
60
+ refresh_preview = (
61
+ f"{refresh_token_str[:8]}...{refresh_token_str[-8:]}"
62
+ if len(refresh_token_str) > 16
63
+ else "***"
64
+ )
65
+
66
+ expires_at = (
67
+ datetime.fromtimestamp(
68
+ self.created_at + self.expires_in, tz=UTC
69
+ ).isoformat()
70
+ if self.expires_in and self.created_at
71
+ else "None"
72
+ )
73
+
74
+ return (
75
+ f"CopilotOAuthToken(access_token='{access_preview}', "
76
+ f"refresh_token='{refresh_preview}', "
77
+ f"expires_at={expires_at}, "
78
+ f"scope='{self.scope}')"
79
+ )
80
+
81
+ @property
82
+ def is_expired(self) -> bool:
83
+ """Check if the token is expired."""
84
+ if not self.expires_in or not self.created_at:
85
+ # If no expiration info, assume not expired
86
+ return False
87
+
88
+ now = datetime.now(UTC).timestamp()
89
+ expires_at = self.created_at + self.expires_in
90
+ return now >= expires_at
91
+
92
+ @property
93
+ def expires_at_datetime(self) -> datetime:
94
+ """Get expiration as datetime object."""
95
+ if not self.expires_in or not self.created_at:
96
+ # Return a far future date if no expiration info
97
+ return datetime.fromtimestamp(2147483647, tz=UTC) # Year 2038
98
+
99
+ return datetime.fromtimestamp(self.created_at + self.expires_in, tz=UTC)
100
+
101
+
102
+ class CopilotEndpoints(BaseModel):
103
+ """Copilot API endpoints configuration."""
104
+
105
+ api: str | None = Field(default=None, description="API endpoint URL")
106
+ origin_tracker: str | None = Field(
107
+ default=None, alias="origin-tracker", description="Origin tracker endpoint URL"
108
+ )
109
+ proxy: str | None = Field(default=None, description="Proxy endpoint URL")
110
+ telemetry: str | None = Field(default=None, description="Telemetry endpoint URL")
111
+
112
+
113
+ class CopilotTokenResponse(BaseModel):
114
+ """Copilot token exchange response."""
115
+
116
+ # Core required fields (backward compatibility)
117
+ token: SecretStr = Field(..., description="Copilot service token")
118
+ expires_at: datetime | None = Field(
119
+ default=None, description="Token expiration datetime"
120
+ )
121
+ refresh_in: int | None = Field(
122
+ default=None, description="Refresh interval in seconds"
123
+ )
124
+
125
+ # Extended optional fields from full API response
126
+ annotations_enabled: bool | None = Field(
127
+ default=None, description="Whether annotations are enabled"
128
+ )
129
+ blackbird_clientside_indexing: bool | None = Field(
130
+ default=None, description="Whether blackbird clientside indexing is enabled"
131
+ )
132
+ chat_enabled: bool | None = Field(
133
+ default=None, description="Whether chat is enabled"
134
+ )
135
+ chat_jetbrains_enabled: bool | None = Field(
136
+ default=None, description="Whether JetBrains chat is enabled"
137
+ )
138
+ code_quote_enabled: bool | None = Field(
139
+ default=None, description="Whether code quote is enabled"
140
+ )
141
+ code_review_enabled: bool | None = Field(
142
+ default=None, description="Whether code review is enabled"
143
+ )
144
+ codesearch: bool | None = Field(
145
+ default=None, description="Whether code search is enabled"
146
+ )
147
+ copilotignore_enabled: bool | None = Field(
148
+ default=None, description="Whether copilotignore is enabled"
149
+ )
150
+ endpoints: CopilotEndpoints | None = Field(
151
+ default=None, description="API endpoints configuration"
152
+ )
153
+ individual: bool | None = Field(
154
+ default=None, description="Whether this is an individual account"
155
+ )
156
+ limited_user_quotas: dict[str, Any] | None = Field(
157
+ default=None, description="Limited user quotas if any"
158
+ )
159
+ limited_user_reset_date: int | None = Field(
160
+ default=None, description="Limited user reset date if any"
161
+ )
162
+ prompt_8k: bool | None = Field(
163
+ default=None, description="Whether 8k prompts are enabled"
164
+ )
165
+ public_suggestions: str | None = Field(
166
+ default=None, description="Public suggestions setting"
167
+ )
168
+ sku: str | None = Field(default=None, description="SKU identifier")
169
+ snippy_load_test_enabled: bool | None = Field(
170
+ default=None, description="Whether snippy load test is enabled"
171
+ )
172
+ telemetry: str | None = Field(default=None, description="Telemetry setting")
173
+ tracking_id: str | None = Field(default=None, description="Tracking ID")
174
+ vsc_electron_fetcher_v2: bool | None = Field(
175
+ default=None, description="Whether VSCode electron fetcher v2 is enabled"
176
+ )
177
+ xcode: bool | None = Field(
178
+ default=None, description="Whether Xcode integration is enabled"
179
+ )
180
+ xcode_chat: bool | None = Field(
181
+ default=None, description="Whether Xcode chat is enabled"
182
+ )
183
+
184
+ @field_serializer("token")
185
+ def serialize_secret(self, value: SecretStr) -> str:
186
+ """Serialize SecretStr to plain string for JSON output."""
187
+ return value.get_secret_value()
188
+
189
+ @field_serializer("expires_at")
190
+ def serialize_datetime(self, value: datetime | None) -> int | None:
191
+ """Serialize datetime back to Unix timestamp."""
192
+ if value is None:
193
+ return None
194
+ return int(value.timestamp())
195
+
196
+ @field_validator("token", mode="before")
197
+ @classmethod
198
+ def validate_token(cls, v: str | SecretStr) -> SecretStr:
199
+ """Convert string values to SecretStr."""
200
+ if isinstance(v, str):
201
+ return SecretStr(v)
202
+ return v
203
+
204
+ @field_validator("expires_at", mode="before")
205
+ @classmethod
206
+ def validate_expires_at(cls, v: int | str | datetime | None) -> datetime | None:
207
+ """Convert integer Unix timestamp or ISO string to datetime object."""
208
+ if v is None:
209
+ return None
210
+ if isinstance(v, datetime):
211
+ return v
212
+ if isinstance(v, int):
213
+ # Convert Unix timestamp to datetime
214
+ return datetime.fromtimestamp(v, tz=UTC)
215
+ if isinstance(v, str):
216
+ # Try to parse as ISO string, fallback to Unix timestamp
217
+ try:
218
+ return datetime.fromisoformat(v.replace("Z", "+00:00"))
219
+ except ValueError:
220
+ try:
221
+ return datetime.fromtimestamp(int(v), tz=UTC)
222
+ except ValueError:
223
+ return None
224
+ return None
225
+
226
+ @property
227
+ def is_expired(self) -> bool:
228
+ """Check if the Copilot token is expired."""
229
+ if not self.expires_at:
230
+ # If no expiration info, assume not expired
231
+ return False
232
+
233
+ now = datetime.now(UTC)
234
+ return now >= self.expires_at
235
+
236
+
237
+ class CopilotCredentials(BaseModel):
238
+ """Copilot credentials containing OAuth and Copilot tokens."""
239
+
240
+ model_config = ConfigDict(
241
+ populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True
242
+ )
243
+
244
+ oauth_token: CopilotOAuthToken = Field(..., description="GitHub OAuth token")
245
+ copilot_token: CopilotTokenResponse | None = Field(
246
+ default=None, description="Copilot service token"
247
+ )
248
+ account_type: str = Field(
249
+ default="individual",
250
+ description="Account type (individual/business/enterprise)",
251
+ )
252
+ created_at: int = Field(
253
+ default_factory=lambda: int(datetime.now(UTC).timestamp()),
254
+ description="Timestamp when credentials were created",
255
+ )
256
+ updated_at: int = Field(
257
+ default_factory=lambda: int(datetime.now(UTC).timestamp()),
258
+ description="Timestamp when credentials were last updated",
259
+ )
260
+
261
+ def __repr__(self) -> str:
262
+ """Safe representation without exposing secrets."""
263
+ copilot_status = "present" if self.copilot_token else "missing"
264
+ return (
265
+ f"CopilotCredentials(oauth_token={repr(self.oauth_token)}, "
266
+ f"copilot_token={copilot_status}, "
267
+ f"account_type='{self.account_type}')"
268
+ )
269
+
270
+ def is_expired(self) -> bool:
271
+ """Check if credentials are expired (BaseCredentials protocol)."""
272
+ return self.oauth_token.is_expired
273
+
274
+ def to_dict(self) -> dict[str, Any]:
275
+ """Convert to dictionary for storage (BaseCredentials protocol)."""
276
+ return self.model_dump(mode="json")
277
+
278
+ @classmethod
279
+ def from_dict(cls, data: dict[str, Any]) -> "CopilotCredentials":
280
+ """Create from dictionary (BaseCredentials protocol)."""
281
+ return cls.model_validate(data)
282
+
283
+ def refresh_updated_at(self) -> None:
284
+ """Update the updated_at timestamp."""
285
+ self.updated_at = int(datetime.now(UTC).timestamp())
286
+
287
+
288
+ class CopilotProfileInfo(BaseProfileInfo):
289
+ """GitHub profile information for Copilot users."""
290
+
291
+ # Required fields from BaseProfileInfo
292
+ account_id: str = Field(..., description="GitHub user ID")
293
+ provider_type: str = Field(default="copilot", description="Provider type")
294
+
295
+ # GitHub-specific fields
296
+ login: str = Field(..., description="GitHub username")
297
+ name: str | None = Field(default=None, description="Full name")
298
+ avatar_url: str | None = Field(default=None, description="Avatar URL")
299
+ html_url: str | None = Field(default=None, description="Profile URL")
300
+ copilot_plan: str | None = Field(
301
+ default=None, description="Copilot subscription plan"
302
+ )
303
+ copilot_access: bool = Field(default=False, description="Has Copilot access")
304
+
305
+ @computed_field
306
+ def computed_display_name(self) -> str:
307
+ """Display name for UI."""
308
+ if self.display_name:
309
+ return self.display_name
310
+ return self.name or self.login
311
+
312
+
313
+ class CopilotTokenInfo(BaseTokenInfo):
314
+ """Token information for Copilot credentials."""
315
+
316
+ provider: Literal["copilot"] = "copilot"
317
+ oauth_expires_at: datetime | None = None
318
+ copilot_expires_at: datetime | None = None
319
+ account_type: str = "individual"
320
+ copilot_access: bool = False
321
+
322
+ @computed_field
323
+ def computed_is_expired(self) -> bool:
324
+ """Check if any token is expired."""
325
+ now = datetime.now(UTC)
326
+
327
+ # Check OAuth token expiration
328
+ if self.oauth_expires_at and now >= self.oauth_expires_at:
329
+ return True
330
+
331
+ # Check Copilot token expiration if available
332
+ return bool(self.copilot_expires_at and now >= self.copilot_expires_at)
333
+
334
+ @computed_field
335
+ def computed_display_name(self) -> str:
336
+ """Display name for UI."""
337
+ return f"GitHub Copilot ({self.account_type})"
338
+
339
+
340
+ class DeviceCodeResponse(BaseModel):
341
+ """GitHub device code authorization response."""
342
+
343
+ device_code: str = Field(..., description="Device verification code")
344
+ user_code: str = Field(..., description="User verification code")
345
+ verification_uri: str = Field(..., description="Verification URL")
346
+ expires_in: int = Field(..., description="Code expiration time in seconds")
347
+ interval: int = Field(..., description="Polling interval in seconds")
348
+
349
+
350
+ class DeviceTokenPollResponse(BaseModel):
351
+ """Response from device code token polling."""
352
+
353
+ access_token: str | None = Field(
354
+ default=None, description="Access token if authorized"
355
+ )
356
+ token_type: str | None = Field(default=None, description="Token type")
357
+ scope: str | None = Field(default=None, description="Granted scopes")
358
+ error: str | None = Field(default=None, description="Error code if any")
359
+ error_description: str | None = Field(default=None, description="Error description")
360
+ error_uri: str | None = Field(default=None, description="Error URI")
361
+
362
+ @property
363
+ def is_pending(self) -> bool:
364
+ """Check if authorization is still pending."""
365
+ return self.error == "authorization_pending"
366
+
367
+ @property
368
+ def is_slow_down(self) -> bool:
369
+ """Check if we should slow down polling."""
370
+ return self.error == "slow_down"
371
+
372
+ @property
373
+ def is_expired(self) -> bool:
374
+ """Check if device code has expired."""
375
+ return self.error == "expired_token"
376
+
377
+ @property
378
+ def is_denied(self) -> bool:
379
+ """Check if user denied authorization."""
380
+ return self.error == "access_denied"
381
+
382
+ @property
383
+ def is_success(self) -> bool:
384
+ """Check if authorization was successful."""
385
+ return self.access_token is not None and self.error is None