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
ccproxy/models/types.py DELETED
@@ -1,102 +0,0 @@
1
- """Common type aliases used across the ccproxy models."""
2
-
3
- from typing import Literal, TypeAlias
4
-
5
- from typing_extensions import TypedDict
6
-
7
-
8
- # Message and content types
9
- MessageRole: TypeAlias = Literal["user", "assistant", "system", "tool"]
10
- OpenAIMessageRole: TypeAlias = Literal[
11
- "system", "user", "assistant", "tool", "developer"
12
- ]
13
- ContentBlockType: TypeAlias = Literal[
14
- "text", "image", "image_url", "tool_use", "thinking"
15
- ]
16
- OpenAIContentType: TypeAlias = Literal["text", "image_url"]
17
-
18
- # Tool-related types
19
- ToolChoiceType: TypeAlias = Literal["auto", "any", "tool", "none", "required"]
20
- OpenAIToolChoiceType: TypeAlias = Literal["none", "auto", "required"]
21
- ToolType: TypeAlias = Literal["function", "custom"]
22
-
23
- # Response format types
24
- ResponseFormatType: TypeAlias = Literal["text", "json_object", "json_schema"]
25
-
26
- # Service tier types
27
- ServiceTier: TypeAlias = Literal["auto", "standard_only"]
28
-
29
- # Stop reasons (re-exported from messages for convenience)
30
- StopReason: TypeAlias = Literal[
31
- "end_turn",
32
- "max_tokens",
33
- "stop_sequence",
34
- "tool_use",
35
- "pause_turn",
36
- "refusal",
37
- ]
38
-
39
- # OpenAI finish reasons
40
- OpenAIFinishReason: TypeAlias = Literal[
41
- "stop", "length", "tool_calls", "content_filter"
42
- ]
43
-
44
- # Error types
45
- ErrorType: TypeAlias = Literal[
46
- "error",
47
- "rate_limit_error",
48
- "invalid_request_error",
49
- "authentication_error",
50
- "not_found_error",
51
- "overloaded_error",
52
- "internal_server_error",
53
- ]
54
-
55
- # Stream event types
56
- StreamEventType: TypeAlias = Literal[
57
- "message_start",
58
- "message_delta",
59
- "message_stop",
60
- "content_block_start",
61
- "content_block_delta",
62
- "content_block_stop",
63
- "ping",
64
- ]
65
-
66
- # Image source types
67
- ImageSourceType: TypeAlias = Literal["base64", "url"]
68
-
69
- # Modality types
70
- ModalityType: TypeAlias = Literal["text", "audio"]
71
-
72
- # Reasoning effort types (OpenAI o1 models)
73
- ReasoningEffort: TypeAlias = Literal["low", "medium", "high"]
74
-
75
- # OpenAI object types
76
- OpenAIObjectType: TypeAlias = Literal[
77
- "chat.completion", "chat.completion.chunk", "model", "list"
78
- ]
79
-
80
- # Permission behavior types
81
- PermissionBehavior: TypeAlias = Literal["allow", "deny"]
82
-
83
-
84
- # Usage and streaming related types
85
- class UsageData(TypedDict, total=False):
86
- """Token usage data extracted from streaming or non-streaming responses."""
87
-
88
- input_tokens: int | None
89
- output_tokens: int | None
90
- cache_read_input_tokens: int | None
91
- cache_creation_input_tokens: int | None
92
- event_type: StreamEventType | None
93
-
94
-
95
- class StreamingTokenMetrics(TypedDict, total=False):
96
- """Accumulated token metrics during streaming."""
97
-
98
- tokens_input: int | None
99
- tokens_output: int | None
100
- cache_read_tokens: int | None
101
- cache_write_tokens: int | None
102
- cost_usd: float | None
@@ -1,51 +0,0 @@
1
- """
2
- Observability module for the CCProxy API.
3
-
4
- This module provides comprehensive observability capabilities including metrics collection,
5
- structured logging, request context tracking, and observability pipeline management.
6
-
7
- The observability system follows a hybrid architecture that combines:
8
- - Real-time metrics collection and aggregation
9
- - Structured logging with correlation IDs
10
- - Request context propagation across service boundaries
11
- - Pluggable pipeline for metrics export and alerting
12
-
13
- Components:
14
- - metrics: Core metrics collection, aggregation, and export functionality
15
- - logging: Structured logging configuration and context-aware loggers
16
- - context: Request context tracking and correlation across async operations
17
- - pipeline: Observability data pipeline for metrics export and alerting
18
- """
19
-
20
- from .context import (
21
- RequestContext,
22
- get_context_tracker,
23
- request_context,
24
- timed_operation,
25
- tracked_request_context,
26
- )
27
- from .metrics import PrometheusMetrics, get_metrics, reset_metrics
28
- from .pushgateway import (
29
- PushgatewayClient,
30
- get_pushgateway_client,
31
- reset_pushgateway_client,
32
- )
33
-
34
-
35
- __all__ = [
36
- # Configuration
37
- # Context management
38
- "RequestContext",
39
- "request_context",
40
- "tracked_request_context",
41
- "timed_operation",
42
- "get_context_tracker",
43
- # Prometheus metrics
44
- "PrometheusMetrics",
45
- "get_metrics",
46
- "reset_metrics",
47
- # Pushgateway
48
- "PushgatewayClient",
49
- "get_pushgateway_client",
50
- "reset_pushgateway_client",
51
- ]
@@ -1,457 +0,0 @@
1
- """Unified access logging utilities for comprehensive request tracking.
2
-
3
- This module provides centralized access logging functionality that can be used
4
- across different parts of the application to generate consistent, comprehensive
5
- access logs with complete request metadata including token usage and costs.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import time
11
- from typing import TYPE_CHECKING, Any
12
-
13
- import structlog
14
-
15
-
16
- if TYPE_CHECKING:
17
- from ccproxy.observability.context import RequestContext
18
- from ccproxy.observability.metrics import PrometheusMetrics
19
- from ccproxy.observability.storage.duckdb_simple import (
20
- AccessLogPayload,
21
- SimpleDuckDBStorage,
22
- )
23
-
24
-
25
- logger = structlog.get_logger(__name__)
26
-
27
-
28
- async def log_request_access(
29
- context: RequestContext,
30
- status_code: int | None = None,
31
- client_ip: str | None = None,
32
- user_agent: str | None = None,
33
- method: str | None = None,
34
- path: str | None = None,
35
- query: str | None = None,
36
- error_message: str | None = None,
37
- storage: SimpleDuckDBStorage | None = None,
38
- metrics: PrometheusMetrics | None = None,
39
- **additional_metadata: Any,
40
- ) -> None:
41
- """Log comprehensive access information for a request.
42
-
43
- This function generates a unified access log entry with complete request
44
- metadata including timing, tokens, costs, and any additional context.
45
- Also stores the access log in DuckDB if available and records Prometheus metrics.
46
-
47
- Args:
48
- context: Request context with timing and metadata
49
- status_code: HTTP status code
50
- client_ip: Client IP address
51
- user_agent: User agent string
52
- method: HTTP method
53
- path: Request path
54
- query: Query parameters
55
- error_message: Error message if applicable
56
- storage: DuckDB storage instance (optional)
57
- metrics: PrometheusMetrics instance for recording metrics (optional)
58
- **additional_metadata: Any additional fields to include
59
- """
60
- # Extract basic request info from context metadata if not provided
61
- ctx_metadata = context.metadata
62
- method = method or ctx_metadata.get("method")
63
- path = path or ctx_metadata.get("path")
64
- status_code = status_code or ctx_metadata.get("status_code")
65
-
66
- # Prepare basic log data (always included)
67
- log_data = {
68
- "request_id": context.request_id,
69
- "method": method,
70
- "path": path,
71
- "query": query,
72
- "client_ip": client_ip,
73
- "user_agent": user_agent,
74
- }
75
-
76
- # Add response-specific fields (only for completed requests)
77
- is_streaming = ctx_metadata.get("streaming", False)
78
- is_streaming_complete = ctx_metadata.get("event_type", "") == "streaming_complete"
79
-
80
- # Include response fields only if this is not a streaming start
81
- if not is_streaming or is_streaming_complete or ctx_metadata.get("error"):
82
- log_data.update(
83
- {
84
- "status_code": status_code,
85
- "duration_ms": context.duration_ms,
86
- "duration_seconds": context.duration_seconds,
87
- "error_message": error_message,
88
- }
89
- )
90
-
91
- # Add token and cost metrics if available
92
- token_fields = [
93
- "tokens_input",
94
- "tokens_output",
95
- "cache_read_tokens",
96
- "cache_write_tokens",
97
- "cost_usd",
98
- "cost_sdk_usd",
99
- "num_turns",
100
- ]
101
-
102
- for field in token_fields:
103
- value = ctx_metadata.get(field)
104
- if value is not None:
105
- log_data[field] = value
106
-
107
- # Add service and endpoint info
108
- service_fields = ["endpoint", "model", "streaming", "service_type", "headers"]
109
-
110
- for field in service_fields:
111
- value = ctx_metadata.get(field)
112
- if value is not None:
113
- log_data[field] = value
114
-
115
- # Add session context metadata if available
116
- session_fields = [
117
- "session_id",
118
- "session_type", # "session_pool" or "direct"
119
- "session_status", # active, idle, connecting, etc.
120
- "session_age_seconds", # how long session has been alive
121
- "session_message_count", # number of messages in session
122
- "session_pool_enabled", # whether session pooling is enabled
123
- "session_idle_seconds", # how long since last activity
124
- "session_error_count", # number of errors in this session
125
- "session_is_new", # whether this is a newly created session
126
- ]
127
-
128
- for field in session_fields:
129
- value = ctx_metadata.get(field)
130
- if value is not None:
131
- log_data[field] = value
132
-
133
- # Add rate limit headers if available
134
- rate_limit_fields = [
135
- "x-ratelimit-limit",
136
- "x-ratelimit-remaining",
137
- "x-ratelimit-reset",
138
- "anthropic-ratelimit-requests-limit",
139
- "anthropic-ratelimit-requests-remaining",
140
- "anthropic-ratelimit-requests-reset",
141
- "anthropic-ratelimit-tokens-limit",
142
- "anthropic-ratelimit-tokens-remaining",
143
- "anthropic-ratelimit-tokens-reset",
144
- "anthropic_request_id",
145
- ]
146
-
147
- for field in rate_limit_fields:
148
- value = ctx_metadata.get(field)
149
- if value is not None:
150
- log_data[field] = value
151
-
152
- # Add any additional metadata provided
153
- log_data.update(additional_metadata)
154
-
155
- # Remove None values to keep log clean
156
- log_data = {k: v for k, v in log_data.items() if v is not None}
157
-
158
- logger = context.logger.bind(**log_data)
159
-
160
- if context.metadata.get("error"):
161
- logger.warn("access_log", exc_info=context.metadata.get("error"))
162
- elif not is_streaming:
163
- # Log as access_log event (structured logging)
164
- logger.info("access_log")
165
- elif is_streaming_complete:
166
- logger.info("access_log")
167
- else:
168
- # if streaming is true, and not streaming_complete log as debug
169
- # real access_log will come later
170
- logger.info("access_log_streaming_start")
171
-
172
- # Store in DuckDB if available
173
- await _store_access_log(log_data, storage)
174
-
175
- # Emit SSE event for real-time dashboard updates
176
- await _emit_access_event("request_complete", log_data)
177
-
178
- # Record Prometheus metrics if metrics instance is provided
179
- if metrics and not error_message:
180
- # Extract required values for metrics
181
- endpoint = ctx_metadata.get("endpoint", path or "unknown")
182
- model = ctx_metadata.get("model")
183
- service_type = ctx_metadata.get("service_type")
184
-
185
- # Record request count
186
- if method and status_code:
187
- metrics.record_request(
188
- method=method,
189
- endpoint=endpoint,
190
- model=model,
191
- status=status_code,
192
- service_type=service_type,
193
- )
194
-
195
- # Record response time
196
- if context.duration_seconds > 0:
197
- metrics.record_response_time(
198
- duration_seconds=context.duration_seconds,
199
- model=model,
200
- endpoint=endpoint,
201
- service_type=service_type,
202
- )
203
-
204
- # Record token usage
205
- tokens_input = ctx_metadata.get("tokens_input")
206
- if tokens_input:
207
- metrics.record_tokens(
208
- token_count=tokens_input,
209
- token_type="input",
210
- model=model,
211
- service_type=service_type,
212
- )
213
-
214
- tokens_output = ctx_metadata.get("tokens_output")
215
- if tokens_output:
216
- metrics.record_tokens(
217
- token_count=tokens_output,
218
- token_type="output",
219
- model=model,
220
- service_type=service_type,
221
- )
222
-
223
- cache_read_tokens = ctx_metadata.get("cache_read_tokens")
224
- if cache_read_tokens:
225
- metrics.record_tokens(
226
- token_count=cache_read_tokens,
227
- token_type="cache_read",
228
- model=model,
229
- service_type=service_type,
230
- )
231
-
232
- cache_write_tokens = ctx_metadata.get("cache_write_tokens")
233
- if cache_write_tokens:
234
- metrics.record_tokens(
235
- token_count=cache_write_tokens,
236
- token_type="cache_write",
237
- model=model,
238
- service_type=service_type,
239
- )
240
-
241
- # Record cost
242
- cost_usd = ctx_metadata.get("cost_usd")
243
- if cost_usd:
244
- metrics.record_cost(
245
- cost_usd=cost_usd,
246
- model=model,
247
- cost_type="total",
248
- service_type=service_type,
249
- )
250
-
251
- # Record error if there was one
252
- if metrics and error_message:
253
- endpoint = ctx_metadata.get("endpoint", path or "unknown")
254
- model = ctx_metadata.get("model")
255
- service_type = ctx_metadata.get("service_type")
256
-
257
- # Extract error type from error message or use generic
258
- error_type = additional_metadata.get(
259
- "error_type",
260
- type(error_message).__name__
261
- if hasattr(error_message, "__class__")
262
- else "unknown_error",
263
- )
264
-
265
- metrics.record_error(
266
- error_type=error_type,
267
- endpoint=endpoint,
268
- model=model,
269
- service_type=service_type,
270
- )
271
-
272
-
273
- async def _store_access_log(
274
- log_data: dict[str, Any], storage: SimpleDuckDBStorage | None = None
275
- ) -> None:
276
- """Store access log in DuckDB storage if available.
277
-
278
- Args:
279
- log_data: Log data to store
280
- storage: DuckDB storage instance (optional)
281
- """
282
- if not storage:
283
- return
284
-
285
- try:
286
- # Prepare data for DuckDB storage
287
- storage_data: AccessLogPayload = {
288
- "timestamp": time.time(),
289
- "request_id": log_data.get("request_id") or "",
290
- "method": log_data.get("method", ""),
291
- "endpoint": log_data.get("endpoint", log_data.get("path", "")),
292
- "path": log_data.get("path", ""),
293
- "query": log_data.get("query", ""),
294
- "client_ip": log_data.get("client_ip", ""),
295
- "user_agent": log_data.get("user_agent", ""),
296
- "service_type": log_data.get("service_type", ""),
297
- "model": log_data.get("model", ""),
298
- "streaming": log_data.get("streaming", False),
299
- "status_code": log_data.get("status_code", 200),
300
- "duration_ms": log_data.get("duration_ms", 0.0),
301
- "duration_seconds": log_data.get("duration_seconds", 0.0),
302
- "tokens_input": log_data.get("tokens_input", 0),
303
- "tokens_output": log_data.get("tokens_output", 0),
304
- "cache_read_tokens": log_data.get("cache_read_tokens", 0),
305
- "cache_write_tokens": log_data.get("cache_write_tokens", 0),
306
- "cost_usd": log_data.get("cost_usd", 0.0),
307
- "cost_sdk_usd": log_data.get("cost_sdk_usd", 0.0),
308
- "num_turns": log_data.get("num_turns", 0),
309
- # Session context metadata
310
- "session_type": log_data.get("session_type", ""),
311
- "session_status": log_data.get("session_status", ""),
312
- "session_age_seconds": log_data.get("session_age_seconds", 0.0),
313
- "session_message_count": log_data.get("session_message_count", 0),
314
- "session_client_id": log_data.get("session_client_id", ""),
315
- "session_pool_enabled": log_data.get("session_pool_enabled", False),
316
- "session_idle_seconds": log_data.get("session_idle_seconds", 0.0),
317
- "session_error_count": log_data.get("session_error_count", 0),
318
- "session_is_new": log_data.get("session_is_new", True),
319
- }
320
-
321
- # Store asynchronously using queue-based DuckDB (prevents deadlocks)
322
- if storage:
323
- await storage.store_request(storage_data)
324
-
325
- except Exception as e:
326
- # Log error but don't fail the request
327
- logger.error(
328
- "access_log_duckdb_error",
329
- error=str(e),
330
- request_id=log_data.get("request_id"),
331
- )
332
-
333
-
334
- async def _write_to_storage(storage: Any, data: dict[str, Any]) -> None:
335
- """Write data to storage asynchronously."""
336
- try:
337
- await storage.store_request(data)
338
- except Exception as e:
339
- logger.error(
340
- "duckdb_store_error",
341
- error=str(e),
342
- request_id=data.get("request_id"),
343
- )
344
-
345
-
346
- async def _emit_access_event(event_type: str, data: dict[str, Any]) -> None:
347
- """Emit SSE event for real-time dashboard updates."""
348
- try:
349
- from ccproxy.observability.sse_events import emit_sse_event
350
-
351
- # Create event data for SSE (exclude internal fields)
352
- sse_data = {
353
- "request_id": data.get("request_id"),
354
- "method": data.get("method"),
355
- "path": data.get("path"),
356
- "query": data.get("query"),
357
- "status_code": data.get("status_code"),
358
- "client_ip": data.get("client_ip"),
359
- "user_agent": data.get("user_agent"),
360
- "service_type": data.get("service_type"),
361
- "model": data.get("model"),
362
- "streaming": data.get("streaming"),
363
- "duration_ms": data.get("duration_ms"),
364
- "duration_seconds": data.get("duration_seconds"),
365
- "tokens_input": data.get("tokens_input"),
366
- "tokens_output": data.get("tokens_output"),
367
- "cost_usd": data.get("cost_usd"),
368
- "endpoint": data.get("endpoint"),
369
- }
370
-
371
- # Remove None values
372
- sse_data = {k: v for k, v in sse_data.items() if v is not None}
373
-
374
- await emit_sse_event(event_type, sse_data)
375
-
376
- except Exception as e:
377
- # Log error but don't fail the request
378
- logger.debug(
379
- "sse_emit_failed",
380
- event_type=event_type,
381
- error=str(e),
382
- request_id=data.get("request_id"),
383
- )
384
-
385
-
386
- def log_request_start(
387
- request_id: str,
388
- method: str,
389
- path: str,
390
- client_ip: str | None = None,
391
- user_agent: str | None = None,
392
- query: str | None = None,
393
- **additional_metadata: Any,
394
- ) -> None:
395
- """Log request start event with basic information.
396
-
397
- This is used for early/middleware logging when full context isn't available yet.
398
-
399
- Args:
400
- request_id: Request identifier
401
- method: HTTP method
402
- path: Request path
403
- client_ip: Client IP address
404
- user_agent: User agent string
405
- query: Query parameters
406
- **additional_metadata: Any additional fields to include
407
- """
408
- log_data = {
409
- "request_id": request_id,
410
- "method": method,
411
- "path": path,
412
- "client_ip": client_ip,
413
- "user_agent": user_agent,
414
- "query": query,
415
- "event_type": "request_start",
416
- "timestamp": time.time(),
417
- }
418
-
419
- # Add any additional metadata
420
- log_data.update(additional_metadata)
421
-
422
- # Remove None values
423
- log_data = {k: v for k, v in log_data.items() if v is not None}
424
-
425
- logger.debug("access_log_start", **log_data)
426
-
427
- # Emit SSE event for real-time dashboard updates
428
- # Note: This is a synchronous function, so we schedule the async emission
429
- try:
430
- import asyncio
431
-
432
- from ccproxy.observability.sse_events import emit_sse_event
433
-
434
- # Create event data for SSE
435
- sse_data = {
436
- "request_id": request_id,
437
- "method": method,
438
- "path": path,
439
- "client_ip": client_ip,
440
- "user_agent": user_agent,
441
- "query": query,
442
- }
443
-
444
- # Remove None values
445
- sse_data = {k: v for k, v in sse_data.items() if v is not None}
446
-
447
- # Schedule async event emission
448
- asyncio.create_task(emit_sse_event("request_start", sse_data))
449
-
450
- except Exception as e:
451
- # Log error but don't fail the request
452
- logger.debug(
453
- "sse_emit_failed",
454
- event_type="request_start",
455
- error=str(e),
456
- request_id=request_id,
457
- )