ccproxy-api 0.1.2__py3-none-any.whl → 0.1.3__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 (108) hide show
  1. ccproxy/_version.py +2 -2
  2. ccproxy/adapters/openai/__init__.py +1 -2
  3. ccproxy/adapters/openai/adapter.py +218 -180
  4. ccproxy/adapters/openai/streaming.py +247 -65
  5. ccproxy/api/__init__.py +0 -3
  6. ccproxy/api/app.py +173 -40
  7. ccproxy/api/dependencies.py +62 -3
  8. ccproxy/api/middleware/errors.py +3 -7
  9. ccproxy/api/middleware/headers.py +0 -2
  10. ccproxy/api/middleware/logging.py +4 -3
  11. ccproxy/api/middleware/request_content_logging.py +297 -0
  12. ccproxy/api/middleware/request_id.py +5 -0
  13. ccproxy/api/middleware/server_header.py +0 -4
  14. ccproxy/api/routes/__init__.py +9 -1
  15. ccproxy/api/routes/claude.py +23 -32
  16. ccproxy/api/routes/health.py +58 -4
  17. ccproxy/api/routes/mcp.py +171 -0
  18. ccproxy/api/routes/metrics.py +4 -8
  19. ccproxy/api/routes/permissions.py +217 -0
  20. ccproxy/api/routes/proxy.py +0 -53
  21. ccproxy/api/services/__init__.py +6 -0
  22. ccproxy/api/services/permission_service.py +368 -0
  23. ccproxy/api/ui/__init__.py +6 -0
  24. ccproxy/api/ui/permission_handler_protocol.py +33 -0
  25. ccproxy/api/ui/terminal_permission_handler.py +593 -0
  26. ccproxy/auth/conditional.py +2 -2
  27. ccproxy/auth/dependencies.py +1 -1
  28. ccproxy/auth/oauth/models.py +0 -1
  29. ccproxy/auth/oauth/routes.py +1 -3
  30. ccproxy/auth/storage/json_file.py +0 -1
  31. ccproxy/auth/storage/keyring.py +0 -3
  32. ccproxy/claude_sdk/__init__.py +2 -0
  33. ccproxy/claude_sdk/client.py +91 -8
  34. ccproxy/claude_sdk/converter.py +405 -210
  35. ccproxy/claude_sdk/options.py +76 -29
  36. ccproxy/claude_sdk/parser.py +200 -0
  37. ccproxy/claude_sdk/streaming.py +286 -0
  38. ccproxy/cli/commands/__init__.py +5 -2
  39. ccproxy/cli/commands/auth.py +2 -4
  40. ccproxy/cli/commands/permission_handler.py +553 -0
  41. ccproxy/cli/commands/serve.py +30 -12
  42. ccproxy/cli/docker/params.py +0 -4
  43. ccproxy/cli/helpers.py +0 -2
  44. ccproxy/cli/main.py +5 -16
  45. ccproxy/cli/options/claude_options.py +19 -1
  46. ccproxy/cli/options/core_options.py +0 -3
  47. ccproxy/cli/options/security_options.py +0 -2
  48. ccproxy/cli/options/server_options.py +3 -2
  49. ccproxy/config/auth.py +0 -1
  50. ccproxy/config/claude.py +78 -2
  51. ccproxy/config/discovery.py +0 -1
  52. ccproxy/config/docker_settings.py +0 -1
  53. ccproxy/config/loader.py +1 -4
  54. ccproxy/config/scheduler.py +20 -0
  55. ccproxy/config/security.py +7 -2
  56. ccproxy/config/server.py +5 -0
  57. ccproxy/config/settings.py +13 -7
  58. ccproxy/config/validators.py +1 -1
  59. ccproxy/core/async_utils.py +1 -4
  60. ccproxy/core/errors.py +45 -1
  61. ccproxy/core/http_transformers.py +4 -3
  62. ccproxy/core/interfaces.py +2 -2
  63. ccproxy/core/logging.py +97 -95
  64. ccproxy/core/middleware.py +1 -1
  65. ccproxy/core/proxy.py +1 -1
  66. ccproxy/core/transformers.py +1 -1
  67. ccproxy/core/types.py +1 -1
  68. ccproxy/docker/models.py +1 -1
  69. ccproxy/docker/protocol.py +0 -3
  70. ccproxy/models/__init__.py +41 -0
  71. ccproxy/models/claude_sdk.py +420 -0
  72. ccproxy/models/messages.py +45 -18
  73. ccproxy/models/permissions.py +115 -0
  74. ccproxy/models/requests.py +1 -1
  75. ccproxy/models/responses.py +29 -2
  76. ccproxy/observability/access_logger.py +1 -2
  77. ccproxy/observability/context.py +17 -1
  78. ccproxy/observability/metrics.py +1 -3
  79. ccproxy/observability/pushgateway.py +0 -2
  80. ccproxy/observability/stats_printer.py +2 -4
  81. ccproxy/observability/storage/duckdb_simple.py +1 -1
  82. ccproxy/observability/storage/models.py +0 -1
  83. ccproxy/pricing/cache.py +0 -1
  84. ccproxy/pricing/loader.py +5 -21
  85. ccproxy/pricing/updater.py +0 -1
  86. ccproxy/scheduler/__init__.py +1 -0
  87. ccproxy/scheduler/core.py +6 -6
  88. ccproxy/scheduler/manager.py +35 -7
  89. ccproxy/scheduler/registry.py +1 -1
  90. ccproxy/scheduler/tasks.py +127 -2
  91. ccproxy/services/claude_sdk_service.py +220 -328
  92. ccproxy/services/credentials/manager.py +0 -1
  93. ccproxy/services/credentials/oauth_client.py +1 -2
  94. ccproxy/services/proxy_service.py +93 -222
  95. ccproxy/testing/config.py +1 -1
  96. ccproxy/testing/mock_responses.py +0 -1
  97. ccproxy/utils/model_mapping.py +197 -0
  98. ccproxy/utils/models_provider.py +150 -0
  99. ccproxy/utils/simple_request_logger.py +284 -0
  100. ccproxy/utils/version_checker.py +184 -0
  101. {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/METADATA +63 -2
  102. ccproxy_api-0.1.3.dist-info/RECORD +166 -0
  103. ccproxy/cli/commands/permission.py +0 -128
  104. ccproxy_api-0.1.2.dist-info/RECORD +0 -150
  105. /ccproxy/scheduler/{exceptions.py → errors.py} +0 -0
  106. {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/WHEEL +0 -0
  107. {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/entry_points.txt +0 -0
  108. {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -4,25 +4,36 @@ from collections.abc import AsyncIterator
4
4
  from typing import Any
5
5
 
6
6
  import structlog
7
+ from pydantic import BaseModel
7
8
 
8
9
  from ccproxy.core.async_utils import patched_typing
9
10
  from ccproxy.core.errors import ClaudeProxyError, ServiceUnavailableError
11
+ from ccproxy.models import claude_sdk as sdk_models
10
12
  from ccproxy.observability import timed_operation
11
13
 
12
14
 
13
15
  with patched_typing():
14
16
  from claude_code_sdk import (
15
- AssistantMessage,
17
+ AssistantMessage as SDKAssistantMessage,
18
+ )
19
+ from claude_code_sdk import (
16
20
  ClaudeCodeOptions,
17
21
  CLIConnectionError,
18
22
  CLIJSONDecodeError,
19
23
  CLINotFoundError,
20
24
  ProcessError,
21
- ResultMessage,
22
- SystemMessage,
23
- UserMessage,
24
25
  query,
25
26
  )
27
+ from claude_code_sdk import (
28
+ ResultMessage as SDKResultMessage,
29
+ )
30
+ from claude_code_sdk import (
31
+ SystemMessage as SDKSystemMessage,
32
+ )
33
+ from claude_code_sdk import (
34
+ UserMessage as SDKUserMessage,
35
+ )
36
+
26
37
 
27
38
  logger = structlog.get_logger(__name__)
28
39
 
@@ -53,9 +64,14 @@ class ClaudeSDKClient:
53
64
 
54
65
  async def query_completion(
55
66
  self, prompt: str, options: ClaudeCodeOptions, request_id: str | None = None
56
- ) -> AsyncIterator[UserMessage | AssistantMessage | SystemMessage | ResultMessage]:
67
+ ) -> AsyncIterator[
68
+ sdk_models.UserMessage
69
+ | sdk_models.AssistantMessage
70
+ | sdk_models.SystemMessage
71
+ | sdk_models.ResultMessage
72
+ ]:
57
73
  """
58
- Execute a query using the Claude Code SDK.
74
+ Execute a query using the Claude Code SDK and yields strongly-typed Pydantic models.
59
75
 
60
76
  Args:
61
77
  prompt: The prompt string to send to Claude
@@ -63,7 +79,7 @@ class ClaudeSDKClient:
63
79
  request_id: Optional request ID for correlation
64
80
 
65
81
  Yields:
66
- Messages from the Claude Code SDK
82
+ Strongly-typed Pydantic messages from ccproxy.claude_sdk.models
67
83
 
68
84
  Raises:
69
85
  ClaudeSDKError: If the query fails
@@ -75,7 +91,74 @@ class ClaudeSDKClient:
75
91
  message_count = 0
76
92
  async for message in query(prompt=prompt, options=options):
77
93
  message_count += 1
78
- yield message
94
+
95
+ logger.debug(
96
+ "claude_sdk_raw_message_received",
97
+ message_type=type(message).__name__,
98
+ message_count=message_count,
99
+ request_id=request_id,
100
+ has_content=hasattr(message, "content")
101
+ and bool(getattr(message, "content", None)),
102
+ content_preview=str(message)[:150],
103
+ )
104
+
105
+ model_class: type[BaseModel] | None = None
106
+ if isinstance(message, SDKUserMessage):
107
+ model_class = sdk_models.UserMessage
108
+ elif isinstance(message, SDKAssistantMessage):
109
+ model_class = sdk_models.AssistantMessage
110
+ elif isinstance(message, SDKSystemMessage):
111
+ model_class = sdk_models.SystemMessage
112
+ elif isinstance(message, SDKResultMessage):
113
+ model_class = sdk_models.ResultMessage
114
+
115
+ # Convert Claude SDK message to our Pydantic model
116
+ try:
117
+ if hasattr(message, "__dict__"):
118
+ converted_message = model_class.model_validate(
119
+ vars(message)
120
+ )
121
+ else:
122
+ # For dataclass objects, use dataclass.asdict equivalent
123
+ message_dict = {}
124
+ if hasattr(message, "__dataclass_fields__"):
125
+ message_dict = {
126
+ field: getattr(message, field)
127
+ for field in message.__dataclass_fields__
128
+ }
129
+ else:
130
+ # Try to extract common attributes
131
+ for attr in [
132
+ "content",
133
+ "subtype",
134
+ "data",
135
+ "session_id",
136
+ "stop_reason",
137
+ "usage",
138
+ "total_cost_usd",
139
+ ]:
140
+ if hasattr(message, attr):
141
+ message_dict[attr] = getattr(message, attr)
142
+
143
+ converted_message = model_class.model_validate(message_dict)
144
+
145
+ logger.debug(
146
+ "claude_sdk_message_converted_successfully",
147
+ original_type=type(message).__name__,
148
+ converted_type=type(converted_message).__name__,
149
+ message_count=message_count,
150
+ request_id=request_id,
151
+ )
152
+ yield converted_message
153
+ except Exception as e:
154
+ logger.warning(
155
+ "claude_sdk_message_conversion_failed",
156
+ message_type=type(message).__name__,
157
+ model_class=model_class.__name__,
158
+ error=str(e),
159
+ )
160
+ # Skip invalid messages rather than crashing
161
+ continue
79
162
 
80
163
  # Store final metrics
81
164
  op["message_count"] = message_count