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
@@ -1,157 +0,0 @@
1
- """Docker adapter factory for CLI commands.
2
-
3
- This module provides functions to create Docker adapters from CLI settings
4
- and command-line arguments.
5
- """
6
-
7
- import getpass
8
- from pathlib import Path
9
- from typing import Any
10
-
11
- from ccproxy.config.settings import Settings
12
- from ccproxy.docker import (
13
- DockerEnv,
14
- DockerPath,
15
- DockerUserContext,
16
- DockerVolume,
17
- )
18
-
19
-
20
- def _create_docker_adapter_from_settings(
21
- settings: Settings,
22
- docker_image: str | None = None,
23
- docker_env: list[str] | None = None,
24
- docker_volume: list[str] | None = None,
25
- docker_arg: list[str] | None = None,
26
- docker_home: str | None = None,
27
- docker_workspace: str | None = None,
28
- user_mapping_enabled: bool | None = None,
29
- user_uid: int | None = None,
30
- user_gid: int | None = None,
31
- command: list[str] | None = None,
32
- cmd_args: list[str] | None = None,
33
- **kwargs: Any,
34
- ) -> tuple[
35
- str,
36
- list[DockerVolume],
37
- DockerEnv,
38
- list[str] | None,
39
- DockerUserContext | None,
40
- list[str],
41
- ]:
42
- """Convert settings and overrides to Docker adapter parameters.
43
-
44
- Args:
45
- settings: Application settings
46
- docker_image: Override Docker image
47
- docker_env: Additional environment variables
48
- docker_volume: Additional volume mappings
49
- docker_arg: Additional Docker arguments
50
- docker_home: Override home directory
51
- docker_workspace: Override workspace directory
52
- user_mapping_enabled: Override user mapping setting
53
- user_uid: Override user ID
54
- user_gid: Override group ID
55
- command: Command to run in container
56
- cmd_args: Arguments for the command
57
- **kwargs: Additional keyword arguments (ignored)
58
-
59
- Returns:
60
- Tuple of (image, volumes, environment, command, user_context, additional_args)
61
- """
62
- docker_settings = settings.docker
63
-
64
- # Determine effective image
65
- image = docker_image or docker_settings.docker_image
66
-
67
- # Process volumes
68
- volumes: list[DockerVolume] = []
69
-
70
- # Add home/workspace volumes with effective directories
71
- home_dir = docker_home or docker_settings.docker_home_directory
72
- workspace_dir = docker_workspace or docker_settings.docker_workspace_directory
73
-
74
- if home_dir:
75
- volumes.append((str(Path(home_dir)), "/data/home"))
76
- if workspace_dir:
77
- volumes.append((str(Path(workspace_dir)), "/data/workspace"))
78
-
79
- # Add base volumes from settings
80
- for vol_str in docker_settings.docker_volumes:
81
- parts = vol_str.split(":", 2)
82
- if len(parts) >= 2:
83
- volumes.append((parts[0], parts[1]))
84
-
85
- # Add CLI override volumes
86
- if docker_volume:
87
- for vol_str in docker_volume:
88
- parts = vol_str.split(":", 2)
89
- if len(parts) >= 2:
90
- volumes.append((parts[0], parts[1]))
91
-
92
- # Process environment
93
- environment: DockerEnv = docker_settings.docker_environment.copy()
94
-
95
- # Add home/workspace environment variables
96
- if home_dir:
97
- environment["CLAUDE_HOME"] = "/data/home"
98
- if workspace_dir:
99
- environment["CLAUDE_WORKSPACE"] = "/data/workspace"
100
-
101
- # Add CLI override environment
102
- if docker_env:
103
- for env_var in docker_env:
104
- if "=" in env_var:
105
- key, value = env_var.split("=", 1)
106
- environment[key] = value
107
-
108
- # Create user context
109
- user_context = None
110
- effective_mapping_enabled = (
111
- user_mapping_enabled
112
- if user_mapping_enabled is not None
113
- else docker_settings.user_mapping_enabled
114
- )
115
-
116
- if effective_mapping_enabled:
117
- effective_uid = user_uid if user_uid is not None else docker_settings.user_uid
118
- effective_gid = user_gid if user_gid is not None else docker_settings.user_gid
119
-
120
- if effective_uid is not None and effective_gid is not None:
121
- # Create DockerPath instances for user context
122
- home_path = None
123
- workspace_path = None
124
-
125
- if home_dir:
126
- home_path = DockerPath(
127
- host_path=Path(home_dir), container_path="/data/home"
128
- )
129
- if workspace_dir:
130
- workspace_path = DockerPath(
131
- host_path=Path(workspace_dir), container_path="/data/workspace"
132
- )
133
-
134
- # Use a default username if not available
135
- username = getpass.getuser()
136
-
137
- user_context = DockerUserContext(
138
- uid=effective_uid,
139
- gid=effective_gid,
140
- username=username,
141
- home_path=home_path,
142
- workspace_path=workspace_path,
143
- )
144
-
145
- # Build command
146
- final_command = None
147
- if command:
148
- final_command = command.copy()
149
- if cmd_args:
150
- final_command.extend(cmd_args)
151
-
152
- # Additional Docker arguments
153
- additional_args = docker_settings.docker_additional_args.copy()
154
- if docker_arg:
155
- additional_args.extend(docker_arg)
156
-
157
- return image, volumes, environment, final_command, user_context, additional_args
@@ -1,274 +0,0 @@
1
- """Shared Docker parameter definitions for Typer CLI commands.
2
-
3
- This module provides reusable Typer Option definitions for Docker-related
4
- parameters that are used across multiple CLI commands, eliminating duplication.
5
- """
6
-
7
- from typing import Any
8
-
9
- import typer
10
-
11
-
12
- # Docker parameter validation functions moved here to avoid utils dependency
13
-
14
-
15
- def parse_docker_env(
16
- ctx: typer.Context, param: typer.CallbackParam, value: list[str] | None
17
- ) -> list[str]:
18
- """Parse Docker environment variable string."""
19
- if not value:
20
- return []
21
-
22
- parsed = []
23
- for env_str in value:
24
- if not env_str or env_str == "[]":
25
- raise typer.BadParameter(
26
- f"Invalid env format: {env_str}. Expected KEY=VALUE"
27
- )
28
- if "=" not in env_str:
29
- raise typer.BadParameter(
30
- f"Invalid env format: {env_str}. Expected KEY=VALUE"
31
- )
32
- parsed.append(env_str)
33
-
34
- return parsed
35
-
36
-
37
- def parse_docker_volume(
38
- ctx: typer.Context, param: typer.CallbackParam, value: list[str] | None
39
- ) -> list[str]:
40
- """Parse Docker volume string."""
41
- if not value:
42
- return []
43
-
44
- # Import the validation function from config
45
- from ccproxy.config.docker_settings import validate_volume_format
46
-
47
- parsed = []
48
- for volume_str in value:
49
- if not volume_str:
50
- continue
51
- try:
52
- validated_volume = validate_volume_format(volume_str)
53
- parsed.append(validated_volume)
54
- except ValueError as e:
55
- raise typer.BadParameter(str(e)) from e
56
-
57
- return parsed
58
-
59
-
60
- def validate_docker_arg(
61
- ctx: typer.Context, param: typer.CallbackParam, value: list[str] | None
62
- ) -> list[str]:
63
- """Validate Docker argument."""
64
- if not value:
65
- return []
66
-
67
- # Basic validation - ensure arguments don't contain dangerous patterns
68
- validated = []
69
- for arg in value:
70
- if not arg:
71
- continue
72
- # Basic validation - just return the arg for now
73
- validated.append(arg)
74
-
75
- return validated
76
-
77
-
78
- def validate_docker_home(
79
- ctx: typer.Context, param: typer.CallbackParam, value: str | None
80
- ) -> str | None:
81
- """Validate Docker home directory."""
82
- if value is None:
83
- return None
84
-
85
- from ccproxy.config.docker_settings import validate_host_path
86
-
87
- try:
88
- return validate_host_path(value)
89
- except ValueError as e:
90
- raise typer.BadParameter(str(e)) from e
91
-
92
-
93
- def validate_docker_image(
94
- ctx: typer.Context, param: typer.CallbackParam, value: str | None
95
- ) -> str | None:
96
- """Validate Docker image name."""
97
- if value is None:
98
- return None
99
-
100
- if not value:
101
- raise typer.BadParameter("Docker image cannot be empty")
102
-
103
- # Basic validation - no spaces allowed in image names
104
- if " " in value:
105
- raise typer.BadParameter(f"Docker image name cannot contain spaces: {value}")
106
-
107
- return value
108
-
109
-
110
- def validate_docker_workspace(
111
- ctx: typer.Context, param: typer.CallbackParam, value: str | None
112
- ) -> str | None:
113
- """Validate Docker workspace directory."""
114
- if value is None:
115
- return None
116
-
117
- from ccproxy.config.docker_settings import validate_host_path
118
-
119
- try:
120
- return validate_host_path(value)
121
- except ValueError as e:
122
- raise typer.BadParameter(str(e)) from e
123
-
124
-
125
- def validate_user_gid(
126
- ctx: typer.Context, param: typer.CallbackParam, value: int | None
127
- ) -> int | None:
128
- """Validate user GID."""
129
- if value is None:
130
- return None
131
-
132
- if value < 0:
133
- raise typer.BadParameter("GID must be non-negative")
134
-
135
- return value
136
-
137
-
138
- def validate_user_uid(
139
- ctx: typer.Context, param: typer.CallbackParam, value: int | None
140
- ) -> int | None:
141
- """Validate user UID."""
142
- if value is None:
143
- return None
144
-
145
- if value < 0:
146
- raise typer.BadParameter("UID must be non-negative")
147
-
148
- return value
149
-
150
-
151
- def docker_image_option() -> Any:
152
- """Docker image parameter."""
153
- return typer.Option(
154
- None,
155
- "--docker-image",
156
- help="Docker image to use (overrides config)",
157
- )
158
-
159
-
160
- def docker_env_option() -> Any:
161
- """Docker environment variables parameter."""
162
- return typer.Option(
163
- [],
164
- "--docker-env",
165
- help="Environment variables to pass to Docker (KEY=VALUE format, can be used multiple times)",
166
- )
167
-
168
-
169
- def docker_volume_option() -> Any:
170
- """Docker volume mounts parameter."""
171
- return typer.Option(
172
- [],
173
- "--docker-volume",
174
- help="Volume mounts to add (host:container[:options] format, can be used multiple times)",
175
- )
176
-
177
-
178
- def docker_arg_option() -> Any:
179
- """Docker arguments parameter."""
180
- return typer.Option(
181
- [],
182
- "--docker-arg",
183
- help="Additional Docker run arguments (can be used multiple times)",
184
- )
185
-
186
-
187
- def docker_home_option() -> Any:
188
- """Docker home directory parameter."""
189
- return typer.Option(
190
- None,
191
- "--docker-home",
192
- help="Home directory inside Docker container (overrides config)",
193
- )
194
-
195
-
196
- def docker_workspace_option() -> Any:
197
- """Docker workspace directory parameter."""
198
- return typer.Option(
199
- None,
200
- "--docker-workspace",
201
- help="Workspace directory inside Docker container (overrides config)",
202
- )
203
-
204
-
205
- def user_mapping_option() -> Any:
206
- """User mapping parameter."""
207
- return typer.Option(
208
- None,
209
- "--user-mapping/--no-user-mapping",
210
- help="Enable/disable UID/GID mapping (overrides config)",
211
- )
212
-
213
-
214
- def user_uid_option() -> Any:
215
- """User UID parameter."""
216
- return typer.Option(
217
- None,
218
- "--user-uid",
219
- help="User ID to run container as (overrides config)",
220
- min=0,
221
- )
222
-
223
-
224
- def user_gid_option() -> Any:
225
- """User GID parameter."""
226
- return typer.Option(
227
- None,
228
- "--user-gid",
229
- help="Group ID to run container as (overrides config)",
230
- min=0,
231
- )
232
-
233
-
234
- class DockerOptions:
235
- """Container for all Docker-related Typer options.
236
-
237
- This class provides a convenient way to include all Docker-related
238
- options in a command using typed attributes.
239
- """
240
-
241
- def __init__(
242
- self,
243
- docker_image: str | None = None,
244
- docker_env: list[str] | None = None,
245
- docker_volume: list[str] | None = None,
246
- docker_arg: list[str] | None = None,
247
- docker_home: str | None = None,
248
- docker_workspace: str | None = None,
249
- user_mapping_enabled: bool | None = None,
250
- user_uid: int | None = None,
251
- user_gid: int | None = None,
252
- ):
253
- """Initialize Docker options.
254
-
255
- Args:
256
- docker_image: Docker image to use
257
- docker_env: Environment variables list
258
- docker_volume: Volume mounts list
259
- docker_arg: Additional Docker arguments
260
- docker_home: Home directory path
261
- docker_workspace: Workspace directory path
262
- user_mapping_enabled: User mapping flag
263
- user_uid: User ID
264
- user_gid: Group ID
265
- """
266
- self.docker_image = docker_image
267
- self.docker_env = docker_env or []
268
- self.docker_volume = docker_volume or []
269
- self.docker_arg = docker_arg or []
270
- self.docker_home = docker_home
271
- self.docker_workspace = docker_workspace
272
- self.user_mapping_enabled = user_mapping_enabled
273
- self.user_uid = user_uid
274
- self.user_gid = user_gid
ccproxy/config/auth.py DELETED
@@ -1,153 +0,0 @@
1
- """Authentication and credentials configuration."""
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from pydantic import BaseModel, Field, field_validator
7
-
8
-
9
- def _get_default_storage_paths() -> list[Path]:
10
- """Get default storage paths"""
11
- return [
12
- Path("~/.config/ccproxy/credentials.json"),
13
- Path("~/.claude/.credentials.json"),
14
- Path("~/.config/claude/.credentials.json"),
15
- ]
16
-
17
-
18
- class OAuthSettings(BaseModel):
19
- """OAuth-specific settings."""
20
-
21
- base_url: str = Field(
22
- default="https://console.anthropic.com",
23
- description="Base URL for OAuth API endpoints",
24
- )
25
- beta_version: str = Field(
26
- default="oauth-2025-04-20",
27
- description="OAuth beta version header",
28
- )
29
- token_url: str = Field(
30
- default="https://console.anthropic.com/v1/oauth/token",
31
- description="OAuth token endpoint URL",
32
- )
33
- authorize_url: str = Field(
34
- default="https://claude.ai/oauth/authorize",
35
- description="OAuth authorization endpoint URL",
36
- )
37
- profile_url: str = Field(
38
- default="https://api.anthropic.com/api/oauth/profile",
39
- description="OAuth profile endpoint URL",
40
- )
41
- client_id: str = Field(
42
- default="9d1c250a-e61b-44d9-88ed-5944d1962f5e",
43
- description="OAuth client ID",
44
- )
45
- redirect_uri: str = Field(
46
- default="http://localhost:54545/callback",
47
- description="OAuth redirect URI",
48
- )
49
- scopes: list[str] = Field(
50
- default_factory=lambda: [
51
- "org:create_api_key",
52
- "user:profile",
53
- "user:inference",
54
- ],
55
- description="OAuth scopes to request",
56
- )
57
- request_timeout: int = Field(
58
- default=30,
59
- description="Timeout in seconds for OAuth requests",
60
- )
61
- user_agent: str = Field(
62
- default="Claude-Code/1.0.43",
63
- description="User agent string for OAuth requests",
64
- )
65
- callback_timeout: int = Field(
66
- default=300,
67
- description="Timeout in seconds for OAuth callback",
68
- ge=60,
69
- le=600,
70
- )
71
- callback_port: int = Field(
72
- default=54545,
73
- description="Port for OAuth callback server",
74
- ge=1024,
75
- le=65535,
76
- )
77
-
78
-
79
- class CredentialStorageSettings(BaseModel):
80
- """Settings for credential storage locations."""
81
-
82
- storage_paths: list[Path] = Field(
83
- default_factory=lambda: _get_default_storage_paths(),
84
- description="Paths to search for credentials files",
85
- )
86
- auto_refresh: bool = Field(
87
- default=True,
88
- description="Automatically refresh expired tokens",
89
- )
90
- refresh_buffer_seconds: int = Field(
91
- default=300,
92
- description="Refresh token this many seconds before expiry",
93
- ge=0,
94
- )
95
-
96
-
97
- class AuthSettings(BaseModel):
98
- """Combined authentication and credentials configuration."""
99
-
100
- oauth: OAuthSettings = Field(
101
- default_factory=OAuthSettings,
102
- description="OAuth configuration",
103
- )
104
- storage: CredentialStorageSettings = Field(
105
- default_factory=CredentialStorageSettings,
106
- description="Credential storage configuration",
107
- )
108
-
109
- @field_validator("oauth", mode="before")
110
- @classmethod
111
- def validate_oauth(cls, v: Any) -> Any:
112
- """Validate and convert OAuth configuration."""
113
- if v is None:
114
- return OAuthSettings()
115
-
116
- # If it's already an OAuthSettings instance, return as-is
117
- if isinstance(v, OAuthSettings):
118
- return v
119
-
120
- # If it's a dict, create OAuthSettings from it
121
- if isinstance(v, dict):
122
- return OAuthSettings(**v)
123
-
124
- # Try to convert to dict if possible
125
- if hasattr(v, "model_dump"):
126
- return OAuthSettings(**v.model_dump())
127
- elif hasattr(v, "__dict__"):
128
- return OAuthSettings(**v.__dict__)
129
-
130
- return v
131
-
132
- @field_validator("storage", mode="before")
133
- @classmethod
134
- def validate_storage(cls, v: Any) -> Any:
135
- """Validate and convert storage configuration."""
136
- if v is None:
137
- return CredentialStorageSettings()
138
-
139
- # If it's already a CredentialStorageSettings instance, return as-is
140
- if isinstance(v, CredentialStorageSettings):
141
- return v
142
-
143
- # If it's a dict, create CredentialStorageSettings from it
144
- if isinstance(v, dict):
145
- return CredentialStorageSettings(**v)
146
-
147
- # Try to convert to dict if possible
148
- if hasattr(v, "model_dump"):
149
- return CredentialStorageSettings(**v.model_dump())
150
- elif hasattr(v, "__dict__"):
151
- return CredentialStorageSettings(**v.__dict__)
152
-
153
- return v