ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0a4__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 +434 -219
  3. ccproxy/api/bootstrap.py +30 -0
  4. ccproxy/api/decorators.py +85 -0
  5. ccproxy/api/dependencies.py +144 -168
  6. ccproxy/api/format_validation.py +54 -0
  7. ccproxy/api/middleware/cors.py +6 -3
  8. ccproxy/api/middleware/errors.py +388 -524
  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 +540 -19
  97. ccproxy/data/codex_headers_fallback.json +114 -7
  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 +61 -105
  329. ccproxy/scheduler/registry.py +6 -32
  330. ccproxy/scheduler/tasks.py +268 -276
  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 +68 -446
  387. ccproxy/utils/version_checker.py +273 -6
  388. ccproxy_api-0.2.0a4.dist-info/METADATA +212 -0
  389. ccproxy_api-0.2.0a4.dist-info/RECORD +417 -0
  390. {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/WHEEL +1 -1
  391. ccproxy_api-0.2.0a4.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 -1251
  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 -243
  458. ccproxy/services/codex_detection_service.py +0 -252
  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.7.dist-info/METADATA +0 -615
  472. ccproxy_api-0.1.7.dist-info/RECORD +0 -191
  473. ccproxy_api-0.1.7.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.7.dist-info → ccproxy_api-0.2.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,271 @@
1
+ """Service for managing token limits and max_tokens modifications."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from ccproxy.core.logging import get_plugin_logger
8
+
9
+ from .config import MaxTokensConfig
10
+ from .models import MaxTokensModification, ModelTokenLimits, TokenLimitsData
11
+
12
+
13
+ logger = get_plugin_logger(__name__)
14
+
15
+
16
+ class TokenLimitsService:
17
+ """Service for managing model token limits and max_tokens modifications."""
18
+
19
+ def __init__(self, config: MaxTokensConfig):
20
+ """Initialize token limits service."""
21
+ self.config = config
22
+ self.token_limits_data = TokenLimitsData()
23
+ self._pricing_cache_path = (
24
+ Path.home() / ".cache" / "ccproxy" / "model_pricing.json"
25
+ )
26
+
27
+ if self.config.prioritize_local_file:
28
+ # Load local file first (takes precedence)
29
+ self._load_limits_from_local_file()
30
+ self._load_limits_from_pricing_cache()
31
+ else:
32
+ # Load pricing cache first, local file as fallback
33
+ self._load_limits_from_pricing_cache()
34
+ self._load_limits_from_local_file()
35
+
36
+ def _load_limits_from_pricing_cache(self) -> None:
37
+ """Load token limits from pricing plugin cache."""
38
+ logger.debug(
39
+ "loading_token_limits_from_pricing_cache",
40
+ cache_path=str(self._pricing_cache_path),
41
+ )
42
+
43
+ if not self._pricing_cache_path.exists():
44
+ logger.warning(
45
+ "pricing_cache_not_found_plugin_will_not_modify_requests",
46
+ cache_path=str(self._pricing_cache_path),
47
+ message="max_tokens plugin requires pricing cache to operate",
48
+ )
49
+ return
50
+
51
+ try:
52
+ with self._pricing_cache_path.open("r", encoding="utf-8") as f:
53
+ pricing_data = json.load(f)
54
+
55
+ loaded_count = 0
56
+ for model_name, model_data in pricing_data.items():
57
+ # Skip non-dict entries (like headers or metadata)
58
+ if not isinstance(model_data, dict):
59
+ continue
60
+
61
+ # Skip image generation models and other non-text models
62
+ if model_data.get("mode") == "image_generation":
63
+ continue
64
+
65
+ # Extract max_output_tokens (prefer this over max_tokens)
66
+ max_output = model_data.get("max_output_tokens") or model_data.get(
67
+ "max_tokens"
68
+ )
69
+ max_input = model_data.get("max_input_tokens")
70
+
71
+ # Skip if values are not integers (e.g., documentation strings)
72
+ if not isinstance(max_output, int):
73
+ continue
74
+
75
+ if max_output:
76
+ self.token_limits_data.models[model_name] = ModelTokenLimits(
77
+ max_output_tokens=max_output,
78
+ max_input_tokens=max_input
79
+ if isinstance(max_input, int)
80
+ else None,
81
+ )
82
+ loaded_count += 1
83
+
84
+ logger.debug(
85
+ "token_limits_loaded_from_pricing_cache",
86
+ model_count=loaded_count,
87
+ cache_path=str(self._pricing_cache_path),
88
+ )
89
+
90
+ except Exception as e:
91
+ logger.error(
92
+ "failed_to_load_pricing_cache_plugin_will_not_modify_requests",
93
+ cache_path=str(self._pricing_cache_path),
94
+ error=str(e),
95
+ exc_info=e,
96
+ )
97
+
98
+ def _load_limits_from_local_file(self) -> None:
99
+ """Load token limits from local token_limits.json file."""
100
+ local_file_path = Path(self.config.default_token_limits_file)
101
+
102
+ logger.debug(
103
+ "loading_token_limits_from_local_file",
104
+ file_path=str(local_file_path),
105
+ )
106
+
107
+ if not local_file_path.exists():
108
+ logger.debug(
109
+ "local_token_limits_file_not_found",
110
+ file_path=str(local_file_path),
111
+ )
112
+ return
113
+
114
+ try:
115
+ with local_file_path.open("r", encoding="utf-8") as f:
116
+ local_data = json.load(f)
117
+
118
+ # Handle flat structure like pricing cache (no nested "models" object)
119
+ models_data = {}
120
+ if "models" in local_data:
121
+ # Old format with nested models
122
+ models_data = local_data.get("models", {})
123
+ if not isinstance(models_data, dict):
124
+ logger.warning(
125
+ "invalid_local_token_limits_format",
126
+ file_path=str(local_file_path),
127
+ reason="models section is not a dictionary",
128
+ )
129
+ return
130
+ else:
131
+ # New flat format like pricing cache
132
+ models_data = {
133
+ k: v
134
+ for k, v in local_data.items()
135
+ if not k.startswith("_") and isinstance(v, dict)
136
+ }
137
+
138
+ loaded_count = 0
139
+ for model_name, model_limits in models_data.items():
140
+ if not isinstance(model_limits, dict):
141
+ continue
142
+
143
+ max_output = model_limits.get("max_output_tokens")
144
+ max_input = model_limits.get("max_input_tokens")
145
+
146
+ if isinstance(max_output, int) and max_output > 0:
147
+ if self.config.prioritize_local_file:
148
+ # Local file values take precedence over pricing cache
149
+ self.token_limits_data.models[model_name] = ModelTokenLimits(
150
+ max_output_tokens=max_output,
151
+ max_input_tokens=max_input
152
+ if isinstance(max_input, int) and max_input > 0
153
+ else None,
154
+ )
155
+ loaded_count += 1
156
+ else:
157
+ # Local file is fallback - only add if model doesn't exist
158
+ if model_name not in self.token_limits_data.models:
159
+ self.token_limits_data.models[model_name] = (
160
+ ModelTokenLimits(
161
+ max_output_tokens=max_output,
162
+ max_input_tokens=max_input
163
+ if isinstance(max_input, int) and max_input > 0
164
+ else None,
165
+ )
166
+ )
167
+ loaded_count += 1
168
+
169
+ logger.debug(
170
+ "token_limits_loaded_from_local_file",
171
+ file_path=str(local_file_path),
172
+ model_count=loaded_count,
173
+ )
174
+
175
+ except Exception as e:
176
+ logger.error(
177
+ "failed_to_load_local_token_limits_file",
178
+ file_path=str(local_file_path),
179
+ error=str(e),
180
+ exc_info=e,
181
+ )
182
+
183
+ def get_max_output_tokens(self, model_name: str) -> int | None:
184
+ """Get maximum output tokens for a model."""
185
+ return self.token_limits_data.get_max_output_tokens(model_name)
186
+
187
+ def should_modify_max_tokens(
188
+ self, request_data: dict[str, Any], model: str
189
+ ) -> tuple[bool, str]:
190
+ """Determine if max_tokens should be modified for the request."""
191
+ current_max_tokens = request_data.get("max_tokens")
192
+
193
+ # Enforce mode: always modify to set max_tokens to model limit
194
+ if self.config.enforce_mode:
195
+ return True, "enforced"
196
+
197
+ # Case 1: No max_tokens provided
198
+ if current_max_tokens is None:
199
+ return True, "missing"
200
+
201
+ # Case 2: Invalid max_tokens (not a positive integer)
202
+ if not isinstance(current_max_tokens, int) or current_max_tokens <= 0:
203
+ return True, "invalid"
204
+
205
+ # Case 3: Max tokens exceeds model limit
206
+ model_limit = self.get_max_output_tokens(model)
207
+ if model_limit and current_max_tokens > model_limit:
208
+ return True, "exceeded"
209
+
210
+ # No modification needed
211
+ return False, "none"
212
+
213
+ def modify_max_tokens(
214
+ self, request_data: dict[str, Any], model: str, provider: str | None = None
215
+ ) -> tuple[dict[str, Any], MaxTokensModification | None]:
216
+ """Modify max_tokens in request data if needed."""
217
+ should_modify, reason_type = self.should_modify_max_tokens(request_data, model)
218
+
219
+ if not should_modify:
220
+ return request_data, None
221
+
222
+ original_max_tokens = request_data.get("max_tokens")
223
+
224
+ # Determine the appropriate max_tokens value
225
+ model_limit = self.get_max_output_tokens(model)
226
+
227
+ if model_limit:
228
+ new_max_tokens = model_limit
229
+ else:
230
+ # Use fallback when model limit is unknown
231
+ new_max_tokens = self.config.fallback_max_tokens
232
+ logger.debug(
233
+ "using_fallback_max_tokens",
234
+ model=model,
235
+ fallback=self.config.fallback_max_tokens,
236
+ )
237
+
238
+ # Create modification info
239
+ modification = MaxTokensModification(
240
+ original_max_tokens=original_max_tokens,
241
+ new_max_tokens=new_max_tokens,
242
+ model=model,
243
+ reason=self.config.get_modification_reason(reason_type),
244
+ )
245
+
246
+ # Create modified request data
247
+ modified_data = request_data.copy()
248
+ modified_data["max_tokens"] = new_max_tokens
249
+
250
+ if self.config.log_modifications:
251
+ logger.info(
252
+ "max_tokens_modified",
253
+ model=model,
254
+ provider=provider,
255
+ original=original_max_tokens,
256
+ new=new_max_tokens,
257
+ reason=modification.reason,
258
+ )
259
+
260
+ return modified_data, modification
261
+
262
+ def initialize(self) -> None:
263
+ """Initialize the service."""
264
+ logger.debug(
265
+ "token_limits_service_initialized",
266
+ models_count=len(self.token_limits_data.models),
267
+ pricing_cache=str(self._pricing_cache_path),
268
+ fallback=self.config.fallback_max_tokens,
269
+ enforce_mode=self.config.enforce_mode,
270
+ prioritize_local_file=self.config.prioritize_local_file,
271
+ )
@@ -0,0 +1,54 @@
1
+ {
2
+ "claude-opus-4-1-20250805": {
3
+ "max_output_tokens": 32000,
4
+ "max_input_tokens": 200000
5
+ },
6
+ "claude-opus-4-20250514": {
7
+ "max_output_tokens": 32000,
8
+ "max_input_tokens": 200000
9
+ },
10
+ "claude-sonnet-4-20250514": {
11
+ "max_output_tokens": 64000,
12
+ "max_input_tokens": 1000000
13
+ },
14
+ "claude-3-7-sonnet-20250219": {
15
+ "max_output_tokens": 8192,
16
+ "max_input_tokens": 200000
17
+ },
18
+ "claude-3-5-sonnet-20241022": {
19
+ "max_output_tokens": 8192,
20
+ "max_input_tokens": 200000
21
+ },
22
+ "claude-3-5-haiku-20241022": {
23
+ "max_output_tokens": 8192,
24
+ "max_input_tokens": 200000
25
+ },
26
+ "claude-3-opus-20240229": {
27
+ "max_output_tokens": 4096,
28
+ "max_input_tokens": 200000
29
+ },
30
+ "claude-3-sonnet-20240229": {
31
+ "max_output_tokens": 4096,
32
+ "max_input_tokens": 200000
33
+ },
34
+ "claude-3-haiku-20240307": {
35
+ "max_output_tokens": 4096,
36
+ "max_input_tokens": 200000
37
+ },
38
+ "gpt-5": {
39
+ "max_output_tokens": 128000,
40
+ "max_input_tokens": 272000
41
+ },
42
+ "gpt-5-codex": {
43
+ "max_output_tokens": 128000,
44
+ "max_input_tokens": 272000
45
+ },
46
+ "_metadata": {
47
+ "source": "generated from ~/.cache/ccproxy/model_pricing.json",
48
+ "claude_models_count": 9,
49
+ "codex_models_count": 2,
50
+ "total_models": 11,
51
+ "generated_for": "max_tokens plugin enforce mode support",
52
+ "note": "Flat structure format, uses simple model names for compatibility with request handling"
53
+ }
54
+ }
@@ -0,0 +1,35 @@
1
+ # Metrics Plugin
2
+
3
+ Collects Prometheus-style metrics for CCProxy events and optionally pushes them.
4
+
5
+ ## Highlights
6
+ - Registers a metrics hook to observe request, token, error, and pool events
7
+ - Exposes a `/metrics` endpoint for scraping when enabled
8
+ - Can schedule a Pushgateway task for off-box metrics delivery
9
+
10
+ ## Configuration
11
+ - `MetricsConfig` toggles collection, namespace, endpoint, and pushgateway options
12
+ - Scheduler integration is automatic when push mode is enabled
13
+ - Generate defaults with `python3 scripts/generate_config_from_model.py \
14
+ --format toml --plugin metrics --config-class MetricsConfig`
15
+
16
+ ```toml
17
+ [plugins.metrics]
18
+ # enabled = true
19
+ # namespace = "ccproxy"
20
+ # metrics_endpoint_enabled = true
21
+ # pushgateway_enabled = false
22
+ # pushgateway_job = "ccproxy"
23
+ # pushgateway_push_interval = 60
24
+ # collect_request_metrics = true
25
+ # collect_token_metrics = true
26
+ # collect_cost_metrics = true
27
+ # collect_error_metrics = true
28
+ # collect_pool_metrics = true
29
+ # histogram_buckets = [0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0]
30
+ ```
31
+
32
+ ## Related Components
33
+ - `hook.py`: implements the metrics collector and event handling
34
+ - `routes.py`: builds the FastAPI router for Prometheus scrape format
35
+ - `tasks.py`: Pushgateway task invoked by the scheduler
@@ -0,0 +1,10 @@
1
+ """Metrics plugin for CCProxy.
2
+
3
+ This plugin provides Prometheus metrics collection and export functionality
4
+ using the hook system for event-driven metric updates.
5
+ """
6
+
7
+ from .plugin import factory
8
+
9
+
10
+ __all__ = ["factory"]
@@ -1,5 +1,5 @@
1
1
  """
2
- Prometheus metrics for operational monitoring.
2
+ Prometheus metrics collector for the metrics plugin.
3
3
 
4
4
  This module provides direct prometheus_client integration for fast operational metrics
5
5
  like request counts, response times, and resource usage. These metrics are optimized
@@ -10,7 +10,7 @@ Key features:
10
10
  - Minimal overhead for high-frequency operations
11
11
  - Standard Prometheus metric types (Counter, Histogram, Gauge)
12
12
  - Automatic label management and validation
13
- - Pushgateway integration for batch metric pushing
13
+ - Integration with hook events for metric updates
14
14
  """
15
15
 
16
16
  from __future__ import annotations
@@ -84,10 +84,10 @@ except ImportError:
84
84
  CollectorRegistry = _DummyCollectorRegistry # type: ignore[misc,assignment]
85
85
 
86
86
 
87
- from structlog import get_logger
87
+ from ccproxy.core.logging import get_plugin_logger
88
88
 
89
89
 
90
- logger = get_logger(__name__)
90
+ logger = get_plugin_logger(__name__)
91
91
 
92
92
 
93
93
  class PrometheusMetrics:
@@ -102,7 +102,7 @@ class PrometheusMetrics:
102
102
  self,
103
103
  namespace: str = "ccproxy",
104
104
  registry: CollectorRegistry | None = None,
105
- pushgateway_client: Any | None = None,
105
+ histogram_buckets: list[float] | None = None,
106
106
  ):
107
107
  """
108
108
  Initialize Prometheus metrics.
@@ -110,7 +110,7 @@ class PrometheusMetrics:
110
110
  Args:
111
111
  namespace: Metric name prefix
112
112
  registry: Custom Prometheus registry (uses default if None)
113
- pushgateway_client: Optional pushgateway client for dependency injection
113
+ histogram_buckets: Custom histogram bucket boundaries
114
114
  """
115
115
  if not PROMETHEUS_AVAILABLE:
116
116
  logger.warning(
@@ -127,13 +127,21 @@ class PrometheusMetrics:
127
127
  else:
128
128
  self.registry = registry
129
129
  self._enabled = PROMETHEUS_AVAILABLE
130
- self._pushgateway_client = pushgateway_client
130
+ self._histogram_buckets = histogram_buckets or [
131
+ 0.01,
132
+ 0.05,
133
+ 0.1,
134
+ 0.25,
135
+ 0.5,
136
+ 1.0,
137
+ 2.5,
138
+ 5.0,
139
+ 10.0,
140
+ 25.0,
141
+ ]
131
142
 
132
143
  if self._enabled:
133
144
  self._init_metrics()
134
- # Initialize pushgateway client if not provided via DI
135
- if self._pushgateway_client is None:
136
- self._init_pushgateway()
137
145
 
138
146
  def _init_metrics(self) -> None:
139
147
  """Initialize all Prometheus metric objects."""
@@ -149,7 +157,7 @@ class PrometheusMetrics:
149
157
  f"{self.namespace}_response_duration_seconds",
150
158
  "Response time in seconds",
151
159
  labelnames=["model", "endpoint", "service_type"],
152
- buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0],
160
+ buckets=self._histogram_buckets,
153
161
  registry=self.registry,
154
162
  )
155
163
 
@@ -263,7 +271,7 @@ class PrometheusMetrics:
263
271
 
264
272
  # Set initial system info
265
273
  try:
266
- from ccproxy import __version__
274
+ from ccproxy.core import __version__
267
275
 
268
276
  version = __version__
269
277
  except ImportError:
@@ -279,28 +287,6 @@ class PrometheusMetrics:
279
287
  # Set service as up
280
288
  self.up.labels(job="ccproxy").set(1)
281
289
 
282
- def _init_pushgateway(self) -> None:
283
- """Initialize Pushgateway client if configured (fallback for non-DI usage)."""
284
- try:
285
- # Import here to avoid circular imports
286
- from ccproxy.config.settings import get_settings
287
-
288
- from .pushgateway import PushgatewayClient
289
-
290
- settings = get_settings()
291
-
292
- self._pushgateway_client = PushgatewayClient(settings.observability)
293
-
294
- if self._pushgateway_client.is_enabled():
295
- logger.info(
296
- "pushgateway_initialized: url=%s job=%s",
297
- settings.observability.pushgateway_url,
298
- settings.observability.pushgateway_job,
299
- )
300
- except Exception as e:
301
- logger.warning("pushgateway_init_failed: error=%s", str(e))
302
- self._pushgateway_client = None
303
-
304
290
  def record_request(
305
291
  self,
306
292
  method: str,
@@ -473,57 +459,6 @@ class PrometheusMetrics:
473
459
  """Check if metrics collection is enabled."""
474
460
  return self._enabled
475
461
 
476
- def push_to_gateway(self, method: str = "push") -> bool:
477
- """
478
- Push current metrics to Pushgateway using official prometheus_client methods.
479
-
480
- Args:
481
- method: Push method - "push" (replace), "pushadd" (add), or "delete"
482
-
483
- Returns:
484
- True if push succeeded, False otherwise
485
- """
486
-
487
- if not self._enabled or not self._pushgateway_client:
488
- return False
489
-
490
- result = self._pushgateway_client.push_metrics(self.registry, method)
491
- return bool(result)
492
-
493
- def push_add_to_gateway(self) -> bool:
494
- """
495
- Add current metrics to existing job/instance in Pushgateway (pushadd operation).
496
-
497
- This is useful when you want to add metrics without replacing existing ones.
498
-
499
- Returns:
500
- True if push succeeded, False otherwise
501
- """
502
- return self.push_to_gateway(method="pushadd")
503
-
504
- def delete_from_gateway(self) -> bool:
505
- """
506
- Delete all metrics for the configured job from Pushgateway.
507
-
508
- This removes all metrics associated with the job, useful for cleanup.
509
-
510
- Returns:
511
- True if delete succeeded, False otherwise
512
- """
513
-
514
- if not self._enabled or not self._pushgateway_client:
515
- return False
516
-
517
- result = self._pushgateway_client.delete_metrics()
518
- return bool(result)
519
-
520
- def is_pushgateway_enabled(self) -> bool:
521
- """Check if Pushgateway client is enabled and configured."""
522
- return (
523
- self._pushgateway_client is not None
524
- and self._pushgateway_client.is_enabled()
525
- )
526
-
527
462
  # Claude SDK Pool metrics methods
528
463
 
529
464
  def update_pool_gauges(
@@ -618,71 +553,3 @@ class PrometheusMetrics:
618
553
  return
619
554
 
620
555
  self.pool_clients_active.set(count)
621
-
622
-
623
- # Global metrics instance
624
- _global_metrics: PrometheusMetrics | None = None
625
-
626
-
627
- def get_metrics(
628
- namespace: str = "ccproxy",
629
- registry: CollectorRegistry | None = None,
630
- pushgateway_client: Any | None = None,
631
- settings: Any | None = None,
632
- ) -> PrometheusMetrics:
633
- """
634
- Get or create global metrics instance with dependency injection.
635
-
636
- Args:
637
- namespace: Metric namespace prefix
638
- registry: Custom Prometheus registry
639
- pushgateway_client: Optional pushgateway client for dependency injection
640
- settings: Optional settings instance to avoid circular imports
641
-
642
- Returns:
643
- PrometheusMetrics instance with full pushgateway support:
644
- - push_to_gateway(): Replace all metrics (default)
645
- - push_add_to_gateway(): Add metrics to existing job
646
- - delete_from_gateway(): Delete all metrics for job
647
- """
648
- global _global_metrics
649
-
650
- if _global_metrics is None:
651
- # Create pushgateway client if not provided via DI
652
- if pushgateway_client is None:
653
- from .pushgateway import get_pushgateway_client
654
-
655
- pushgateway_client = get_pushgateway_client()
656
-
657
- _global_metrics = PrometheusMetrics(
658
- namespace=namespace,
659
- registry=registry,
660
- pushgateway_client=pushgateway_client,
661
- )
662
-
663
- return _global_metrics
664
-
665
-
666
- def reset_metrics() -> None:
667
- """Reset global metrics instance (mainly for testing)."""
668
- global _global_metrics
669
- _global_metrics = None
670
-
671
- # Clear Prometheus registry to avoid duplicate metrics in tests
672
- if PROMETHEUS_AVAILABLE:
673
- try:
674
- from prometheus_client import REGISTRY
675
-
676
- # Clear all collectors from the registry
677
- collectors = list(REGISTRY._collector_to_names.keys())
678
- for collector in collectors:
679
- REGISTRY.unregister(collector)
680
- except Exception:
681
- # If clearing the registry fails, just continue
682
- # This is mainly for testing and shouldn't break functionality
683
- pass
684
-
685
- # Also reset pushgateway client
686
- from .pushgateway import reset_pushgateway_client
687
-
688
- reset_pushgateway_client()