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,76 @@
1
+ """Handler configuration for request handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
7
+
8
+ from ccproxy.services.adapters.format_context import FormatContext
9
+
10
+
11
+ if TYPE_CHECKING:
12
+ from ccproxy.services.adapters.format_adapter import FormatAdapterProtocol
13
+
14
+
15
+ @runtime_checkable
16
+ class PluginTransformerProtocol(Protocol):
17
+ """Protocol for plugin-based transformers with header and body methods."""
18
+
19
+ def transform_headers(
20
+ self, headers: dict[str, str], *args: Any, **kwargs: Any
21
+ ) -> dict[str, str]:
22
+ """Transform request headers."""
23
+ ...
24
+
25
+
26
+ @runtime_checkable
27
+ class SSEParserProtocol(Protocol):
28
+ """Protocol for SSE parsers to extract a final JSON response.
29
+
30
+ Implementations should return a parsed dict for the final response, or
31
+ None if no final response could be determined.
32
+ """
33
+
34
+ def __call__(
35
+ self, raw: str
36
+ ) -> dict[str, Any] | None: # pragma: no cover - protocol
37
+ ...
38
+
39
+ def transform_body(self, body: Any) -> Any:
40
+ """Transform request body."""
41
+ ...
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class HandlerConfig:
46
+ """Processing pipeline configuration for HTTP/streaming handlers.
47
+
48
+ This config only contains universal processing concerns,
49
+ not plugin-specific parameters like session_id or access_token.
50
+
51
+ Following the Parameter Object pattern, this groups related processing
52
+ components while maintaining clean separation of concerns. Plugin-specific
53
+ parameters should be passed directly as method parameters.
54
+ """
55
+
56
+ # Format conversion (e.g., OpenAI ↔ Anthropic)
57
+ request_adapter: FormatAdapterProtocol | None = None
58
+ response_adapter: FormatAdapterProtocol | None = None
59
+
60
+ # Header/body transformation
61
+ request_transformer: PluginTransformerProtocol | None = None
62
+ response_transformer: PluginTransformerProtocol | None = None
63
+
64
+ # Feature flag
65
+ supports_streaming: bool = True
66
+
67
+ # Header case preservation toggle for upstream requests
68
+ # When True, the HTTP handler will not canonicalize header names and will
69
+ # forward them with their original casing/order as produced by transformers.
70
+ preserve_header_case: bool = False
71
+
72
+ # Optional SSE parser provided by plugins that return SSE streams
73
+ sse_parser: SSEParserProtocol | None = None
74
+
75
+ # Format context for adapter selection
76
+ format_context: FormatContext | None = None
@@ -0,0 +1,298 @@
1
+ """Service interfaces for explicit dependency injection.
2
+
3
+ This module defines protocol interfaces for core services that adapters need,
4
+ enabling explicit dependency injection and removing the service locator pattern.
5
+ """
6
+
7
+ from collections.abc import AsyncIterator
8
+ from typing import TYPE_CHECKING, Any, Protocol
9
+
10
+ import httpx
11
+ from starlette.responses import Response
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from ccproxy.core.request_context import RequestContext
16
+
17
+
18
+ class IRequestHandler(Protocol):
19
+ """Protocol for request handling functionality.
20
+
21
+ Note: The dispatch_request method has been removed in favor of
22
+ using plugin adapters' handle_request() method directly.
23
+ """
24
+
25
+ pass
26
+
27
+
28
+ class IRequestTracer(Protocol):
29
+ """Request tracing interface."""
30
+
31
+ async def trace_request(
32
+ self,
33
+ request_id: str,
34
+ method: str,
35
+ url: str,
36
+ headers: dict[str, str],
37
+ body: bytes | None = None,
38
+ ) -> None:
39
+ """Trace an outgoing request.
40
+
41
+ Args:
42
+ request_id: Unique request identifier
43
+ method: HTTP method
44
+ url: Target URL
45
+ headers: Request headers
46
+ body: Request body if available
47
+ """
48
+ ...
49
+
50
+ async def trace_response(
51
+ self,
52
+ request_id: str,
53
+ status: int,
54
+ headers: dict[str, str],
55
+ body: bytes | None = None,
56
+ ) -> None:
57
+ """Trace an incoming response.
58
+
59
+ Args:
60
+ request_id: Unique request identifier
61
+ status: HTTP status code
62
+ headers: Response headers
63
+ body: Response body if available
64
+ """
65
+ ...
66
+
67
+ def should_trace(self) -> bool:
68
+ """Check if tracing is enabled.
69
+
70
+ Returns:
71
+ True if tracing should be performed
72
+ """
73
+ ...
74
+
75
+
76
+ class IMetricsCollector(Protocol):
77
+ """Metrics collection interface."""
78
+
79
+ def track_request(
80
+ self, method: str, path: str, provider: str | None = None
81
+ ) -> None:
82
+ """Track an incoming request.
83
+
84
+ Args:
85
+ method: HTTP method
86
+ path: Request path
87
+ provider: Optional provider identifier
88
+ """
89
+ ...
90
+
91
+ def track_response(
92
+ self, status: int, duration: float, provider: str | None = None
93
+ ) -> None:
94
+ """Track a response.
95
+
96
+ Args:
97
+ status: HTTP status code
98
+ duration: Response time in seconds
99
+ provider: Optional provider identifier
100
+ """
101
+ ...
102
+
103
+ def track_error(self, error_type: str, provider: str | None = None) -> None:
104
+ """Track an error occurrence.
105
+
106
+ Args:
107
+ error_type: Type of error
108
+ provider: Optional provider identifier
109
+ """
110
+ ...
111
+
112
+ def track_tokens(
113
+ self,
114
+ input_tokens: int,
115
+ output_tokens: int,
116
+ provider: str | None = None,
117
+ model: str | None = None,
118
+ ) -> None:
119
+ """Track token usage.
120
+
121
+ Args:
122
+ input_tokens: Number of input tokens
123
+ output_tokens: Number of output tokens
124
+ provider: Optional provider identifier
125
+ model: Optional model identifier
126
+ """
127
+ ...
128
+
129
+
130
+ class StreamingMetrics(Protocol):
131
+ """Streaming response handler interface."""
132
+
133
+ async def handle_stream(
134
+ self,
135
+ response: httpx.Response,
136
+ request_context: "RequestContext | None" = None,
137
+ ) -> AsyncIterator[bytes]:
138
+ """Handle a streaming response.
139
+
140
+ Args:
141
+ response: HTTP response object
142
+ request_context: Optional request context
143
+
144
+ Yields:
145
+ Response chunks
146
+ """
147
+ ...
148
+
149
+ def create_streaming_response(
150
+ self,
151
+ stream: AsyncIterator[bytes],
152
+ headers: dict[str, str] | None = None,
153
+ ) -> Response:
154
+ """Create a streaming response.
155
+
156
+ Args:
157
+ stream: Async iterator of response chunks
158
+ headers: Optional response headers
159
+
160
+ Returns:
161
+ Streaming response object
162
+ """
163
+ ...
164
+
165
+ async def handle_streaming_request(
166
+ self,
167
+ method: str,
168
+ url: str,
169
+ headers: dict[str, str],
170
+ body: bytes,
171
+ handler_config: Any,
172
+ request_context: Any,
173
+ client_config: dict[str, Any] | None = None,
174
+ client: httpx.AsyncClient | None = None,
175
+ ) -> Any:
176
+ """Handle a streaming request.
177
+
178
+ Args:
179
+ method: HTTP method
180
+ url: Target URL
181
+ headers: Request headers
182
+ body: Request body
183
+ handler_config: Handler configuration
184
+ request_context: Request context
185
+ client_config: Optional client configuration
186
+ client: Optional HTTP client
187
+
188
+ Returns:
189
+ Deferred streaming response
190
+ """
191
+ ...
192
+
193
+
194
+ # Null implementations for optional dependencies
195
+
196
+
197
+ class NullRequestTracer:
198
+ """Null implementation of request tracer (no-op)."""
199
+
200
+ async def trace_request(
201
+ self,
202
+ request_id: str,
203
+ method: str,
204
+ url: str,
205
+ headers: dict[str, str],
206
+ body: bytes | None = None,
207
+ ) -> None:
208
+ """No-op request tracing."""
209
+ pass
210
+
211
+ async def trace_response(
212
+ self,
213
+ request_id: str,
214
+ status: int,
215
+ headers: dict[str, str],
216
+ body: bytes | None = None,
217
+ ) -> None:
218
+ """No-op response tracing."""
219
+ pass
220
+
221
+ def should_trace(self) -> bool:
222
+ """Always return False for null tracer."""
223
+ return False
224
+
225
+
226
+ class NullMetricsCollector:
227
+ """Null implementation of metrics collector (no-op)."""
228
+
229
+ def track_request(
230
+ self, method: str, path: str, provider: str | None = None
231
+ ) -> None:
232
+ """No-op request tracking."""
233
+ pass
234
+
235
+ def track_response(
236
+ self, status: int, duration: float, provider: str | None = None
237
+ ) -> None:
238
+ """No-op response tracking."""
239
+ pass
240
+
241
+ def track_error(self, error_type: str, provider: str | None = None) -> None:
242
+ """No-op error tracking."""
243
+ pass
244
+
245
+ def track_tokens(
246
+ self,
247
+ input_tokens: int,
248
+ output_tokens: int,
249
+ provider: str | None = None,
250
+ model: str | None = None,
251
+ ) -> None:
252
+ """No-op token tracking."""
253
+ pass
254
+
255
+
256
+ class NullStreamingHandler:
257
+ """Null implementation of streaming handler."""
258
+
259
+ async def handle_stream(
260
+ self,
261
+ response: httpx.Response,
262
+ request_context: "RequestContext | None" = None,
263
+ ) -> AsyncIterator[bytes]:
264
+ """Return empty stream."""
265
+ # Make this a proper async generator
266
+ for _ in []:
267
+ yield b""
268
+
269
+ def create_streaming_response(
270
+ self,
271
+ stream: AsyncIterator[bytes],
272
+ headers: dict[str, str] | None = None,
273
+ ) -> Response:
274
+ """Create empty response."""
275
+ from starlette.responses import Response
276
+
277
+ return Response(content=b"", headers=headers or {})
278
+
279
+ async def handle_streaming_request(
280
+ self,
281
+ method: str,
282
+ url: str,
283
+ headers: dict[str, str],
284
+ body: bytes,
285
+ handler_config: Any,
286
+ request_context: Any,
287
+ client_config: dict[str, Any] | None = None,
288
+ client: httpx.AsyncClient | None = None,
289
+ ) -> Any:
290
+ """Null implementation - returns a simple error response."""
291
+ # For null implementation, return a regular response instead of trying to stream
292
+ from starlette.responses import JSONResponse
293
+
294
+ return JSONResponse(
295
+ content={"error": "Streaming handler not available"},
296
+ status_code=503, # Service Unavailable
297
+ headers={"X-Error": "NullStreamingHandler"},
298
+ )
@@ -0,0 +1,6 @@
1
+ """Mock response handling services for bypass mode."""
2
+
3
+ from ccproxy.services.mocking.mock_handler import MockResponseHandler
4
+
5
+
6
+ __all__ = ["MockResponseHandler"]
@@ -0,0 +1,291 @@
1
+ """Mock response handler for bypass mode."""
2
+
3
+ import asyncio
4
+ import json
5
+ import random
6
+ from collections.abc import AsyncGenerator
7
+ from typing import Any
8
+
9
+ import structlog
10
+ from fastapi.responses import StreamingResponse
11
+
12
+ from ccproxy.core.request_context import RequestContext
13
+ from ccproxy.services.adapters.format_adapter import DictFormatAdapter
14
+ from ccproxy.services.adapters.simple_converters import (
15
+ convert_anthropic_to_openai_response,
16
+ )
17
+ from ccproxy.testing import RealisticMockResponseGenerator
18
+
19
+
20
+ logger = structlog.get_logger(__name__)
21
+
22
+
23
+ class MockResponseHandler:
24
+ """Handles bypass mode with realistic mock responses."""
25
+
26
+ def __init__(
27
+ self,
28
+ mock_generator: RealisticMockResponseGenerator,
29
+ openai_adapter: DictFormatAdapter | None = None,
30
+ error_rate: float = 0.05,
31
+ latency_range: tuple[float, float] = (0.5, 2.0),
32
+ ) -> None:
33
+ """Initialize with mock generator and format adapter.
34
+
35
+ - Uses existing testing utilities
36
+ - Supports both Anthropic and OpenAI formats
37
+ """
38
+ self.mock_generator = mock_generator
39
+ if openai_adapter is None:
40
+ openai_adapter = DictFormatAdapter(
41
+ response=convert_anthropic_to_openai_response,
42
+ name="mock_anthropic_to_openai",
43
+ )
44
+ self.openai_adapter = openai_adapter
45
+ self.error_rate = error_rate
46
+ self.latency_range = latency_range
47
+
48
+ def extract_message_type(self, body: bytes | None) -> str:
49
+ """Analyze request body to determine response type.
50
+
51
+ - Checks for 'tools' field → returns 'tool_use'
52
+ - Analyzes message length → returns 'long'|'medium'|'short'
53
+ - Handles JSON decode errors gracefully
54
+ """
55
+ if not body:
56
+ return "short"
57
+
58
+ try:
59
+ data = json.loads(body)
60
+
61
+ # Check for tool use
62
+ if "tools" in data:
63
+ return "tool_use"
64
+
65
+ # Analyze message content length
66
+ messages = data.get("messages", [])
67
+ if messages:
68
+ total_content_length = sum(
69
+ len(msg.get("content", ""))
70
+ for msg in messages
71
+ if isinstance(msg.get("content"), str)
72
+ )
73
+
74
+ if total_content_length > 1000:
75
+ return "long"
76
+ elif total_content_length > 200:
77
+ return "medium"
78
+
79
+ return "short"
80
+
81
+ except (json.JSONDecodeError, TypeError):
82
+ return "short"
83
+
84
+ def should_simulate_error(self) -> bool:
85
+ """Randomly decide if error should be simulated.
86
+
87
+ - Uses configuration-based error rate
88
+ - Provides realistic error distribution
89
+ """
90
+ return random.random() < self.error_rate
91
+
92
+ async def generate_standard_response(
93
+ self,
94
+ model: str | None,
95
+ is_openai_format: bool,
96
+ ctx: RequestContext,
97
+ message_type: str = "short",
98
+ ) -> tuple[int, dict[str, str], bytes]:
99
+ """Generate non-streaming mock response.
100
+
101
+ - Simulates realistic latency (configurable)
102
+ - Generates appropriate token counts
103
+ - Updates request context with metrics
104
+ - Returns (status_code, headers, body)
105
+ """
106
+ # Simulate latency
107
+ latency = random.uniform(*self.latency_range)
108
+ await asyncio.sleep(latency)
109
+
110
+ # Check if we should simulate an error
111
+ if self.should_simulate_error():
112
+ error_response = self._generate_error_response(is_openai_format)
113
+ return 429, {"content-type": "application/json"}, error_response
114
+
115
+ # Generate mock response based on type
116
+ if message_type == "tool_use":
117
+ mock_response = self.mock_generator.generate_tool_use_response(model=model)
118
+ elif message_type == "long":
119
+ mock_response = self.mock_generator.generate_long_response(model=model)
120
+ elif message_type == "medium":
121
+ mock_response = self.mock_generator.generate_medium_response(model=model)
122
+ else:
123
+ mock_response = self.mock_generator.generate_short_response(model=model)
124
+
125
+ # Convert to OpenAI format if needed
126
+ if is_openai_format and message_type != "tool_use":
127
+ # Use dict-based conversion
128
+ mock_response = await self.openai_adapter.convert_response(mock_response)
129
+
130
+ # Update context with metrics
131
+ if ctx:
132
+ ctx.metrics["mock_response_type"] = message_type
133
+ ctx.metrics["mock_latency_ms"] = int(latency * 1000)
134
+
135
+ headers = {
136
+ "content-type": "application/json",
137
+ "x-request-id": ctx.request_id if ctx else "mock-request",
138
+ }
139
+
140
+ return 200, headers, json.dumps(mock_response).encode()
141
+
142
+ async def generate_streaming_response(
143
+ self,
144
+ model: str | None,
145
+ is_openai_format: bool,
146
+ ctx: RequestContext,
147
+ message_type: str = "short",
148
+ ) -> StreamingResponse:
149
+ """Generate SSE streaming mock response.
150
+
151
+ - Simulates realistic token generation rate
152
+ - Properly formatted SSE events
153
+ - Includes [DONE] marker
154
+ """
155
+
156
+ async def stream_generator() -> AsyncGenerator[bytes, None]:
157
+ # Generate base response
158
+ if message_type == "tool_use":
159
+ base_response = self.mock_generator.generate_tool_use_response(
160
+ model=model
161
+ )
162
+ elif message_type == "long":
163
+ base_response = self.mock_generator.generate_long_response(model=model)
164
+ else:
165
+ base_response = self.mock_generator.generate_short_response(model=model)
166
+
167
+ content = base_response.get("content", [{"text": "Mock response"}])
168
+ if isinstance(content, list) and content:
169
+ text_content = content[0].get("text", "Mock response")
170
+ else:
171
+ text_content = "Mock response"
172
+
173
+ # Split content into chunks
174
+ words = text_content.split()
175
+ chunk_size = 3 # Words per chunk
176
+
177
+ # Send initial event
178
+ if is_openai_format:
179
+ initial_event = {
180
+ "id": f"chatcmpl-{ctx.request_id if ctx else 'mock'}",
181
+ "object": "chat.completion.chunk",
182
+ "created": 1234567890,
183
+ "model": model or "gpt-4",
184
+ "choices": [
185
+ {
186
+ "index": 0,
187
+ "delta": {"role": "assistant"},
188
+ "finish_reason": None,
189
+ }
190
+ ],
191
+ }
192
+ yield f"data: {json.dumps(initial_event)}\n\n".encode()
193
+ else:
194
+ initial_event = {
195
+ "type": "message_start",
196
+ "message": {
197
+ "id": f"msg_{ctx.request_id if ctx else 'mock'}",
198
+ "type": "message",
199
+ "role": "assistant",
200
+ "model": model or "claude-3-opus-20240229",
201
+ "content": [],
202
+ "usage": {"input_tokens": 10, "output_tokens": 0},
203
+ },
204
+ }
205
+ yield f"data: {json.dumps(initial_event)}\n\n".encode()
206
+
207
+ # Stream content chunks
208
+ for i in range(0, len(words), chunk_size):
209
+ chunk_words = words[i : i + chunk_size]
210
+ chunk_text = " ".join(chunk_words)
211
+ if i + chunk_size < len(words):
212
+ chunk_text += " "
213
+
214
+ await asyncio.sleep(0.05) # Simulate token generation delay
215
+
216
+ if is_openai_format:
217
+ chunk_event = {
218
+ "id": f"chatcmpl-{ctx.request_id if ctx else 'mock'}",
219
+ "object": "chat.completion.chunk",
220
+ "created": 1234567890,
221
+ "model": model or "gpt-4",
222
+ "choices": [
223
+ {
224
+ "index": 0,
225
+ "delta": {"content": chunk_text},
226
+ "finish_reason": None,
227
+ }
228
+ ],
229
+ }
230
+ else:
231
+ chunk_event = {
232
+ "type": "content_block_delta",
233
+ "index": 0,
234
+ "delta": {"type": "text_delta", "text": chunk_text},
235
+ }
236
+
237
+ yield f"data: {json.dumps(chunk_event)}\n\n".encode()
238
+
239
+ # Send final event
240
+ if is_openai_format:
241
+ final_event = {
242
+ "id": f"chatcmpl-{ctx.request_id if ctx else 'mock'}",
243
+ "object": "chat.completion.chunk",
244
+ "created": 1234567890,
245
+ "model": model or "gpt-4",
246
+ "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
247
+ }
248
+ yield f"data: {json.dumps(final_event)}\n\n".encode()
249
+ else:
250
+ final_event = {
251
+ "type": "message_stop",
252
+ "message": {
253
+ "usage": {
254
+ "input_tokens": 10,
255
+ "output_tokens": len(text_content.split()),
256
+ }
257
+ },
258
+ }
259
+ yield f"data: {json.dumps(final_event)}\n\n".encode()
260
+
261
+ # Send [DONE] marker
262
+ yield b"data: [DONE]\n\n"
263
+
264
+ return StreamingResponse(
265
+ stream_generator(),
266
+ media_type="text/event-stream",
267
+ headers={
268
+ "Cache-Control": "no-cache",
269
+ "X-Request-ID": ctx.request_id if ctx else "mock-request",
270
+ },
271
+ )
272
+
273
+ def _generate_error_response(self, is_openai_format: bool) -> bytes:
274
+ """Generate a mock error response."""
275
+ if is_openai_format:
276
+ error: dict[str, Any] = {
277
+ "error": {
278
+ "message": "Rate limit exceeded (mock error)",
279
+ "type": "rate_limit_error",
280
+ "code": "rate_limit_exceeded",
281
+ }
282
+ }
283
+ else:
284
+ error = {
285
+ "type": "error",
286
+ "error": {
287
+ "type": "rate_limit_error",
288
+ "message": "Rate limit exceeded (mock error)",
289
+ },
290
+ }
291
+ return json.dumps(error).encode()
@@ -0,0 +1,7 @@
1
+ """Request tracing services for monitoring and debugging."""
2
+
3
+ from ccproxy.services.tracing.interfaces import RequestTracer, StreamingTracer
4
+ from ccproxy.services.tracing.null_tracer import NullRequestTracer
5
+
6
+
7
+ __all__ = ["RequestTracer", "StreamingTracer", "NullRequestTracer"]