ccproxy-api 0.1.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 (148) hide show
  1. ccproxy/__init__.py +4 -0
  2. ccproxy/__main__.py +7 -0
  3. ccproxy/_version.py +21 -0
  4. ccproxy/adapters/__init__.py +11 -0
  5. ccproxy/adapters/base.py +80 -0
  6. ccproxy/adapters/openai/__init__.py +43 -0
  7. ccproxy/adapters/openai/adapter.py +915 -0
  8. ccproxy/adapters/openai/models.py +412 -0
  9. ccproxy/adapters/openai/streaming.py +449 -0
  10. ccproxy/api/__init__.py +28 -0
  11. ccproxy/api/app.py +225 -0
  12. ccproxy/api/dependencies.py +140 -0
  13. ccproxy/api/middleware/__init__.py +11 -0
  14. ccproxy/api/middleware/auth.py +0 -0
  15. ccproxy/api/middleware/cors.py +55 -0
  16. ccproxy/api/middleware/errors.py +703 -0
  17. ccproxy/api/middleware/headers.py +51 -0
  18. ccproxy/api/middleware/logging.py +175 -0
  19. ccproxy/api/middleware/request_id.py +69 -0
  20. ccproxy/api/middleware/server_header.py +62 -0
  21. ccproxy/api/responses.py +84 -0
  22. ccproxy/api/routes/__init__.py +16 -0
  23. ccproxy/api/routes/claude.py +181 -0
  24. ccproxy/api/routes/health.py +489 -0
  25. ccproxy/api/routes/metrics.py +1033 -0
  26. ccproxy/api/routes/proxy.py +238 -0
  27. ccproxy/auth/__init__.py +75 -0
  28. ccproxy/auth/bearer.py +68 -0
  29. ccproxy/auth/credentials_adapter.py +93 -0
  30. ccproxy/auth/dependencies.py +229 -0
  31. ccproxy/auth/exceptions.py +79 -0
  32. ccproxy/auth/manager.py +102 -0
  33. ccproxy/auth/models.py +118 -0
  34. ccproxy/auth/oauth/__init__.py +26 -0
  35. ccproxy/auth/oauth/models.py +49 -0
  36. ccproxy/auth/oauth/routes.py +396 -0
  37. ccproxy/auth/oauth/storage.py +0 -0
  38. ccproxy/auth/storage/__init__.py +12 -0
  39. ccproxy/auth/storage/base.py +57 -0
  40. ccproxy/auth/storage/json_file.py +159 -0
  41. ccproxy/auth/storage/keyring.py +192 -0
  42. ccproxy/claude_sdk/__init__.py +20 -0
  43. ccproxy/claude_sdk/client.py +169 -0
  44. ccproxy/claude_sdk/converter.py +331 -0
  45. ccproxy/claude_sdk/options.py +120 -0
  46. ccproxy/cli/__init__.py +14 -0
  47. ccproxy/cli/commands/__init__.py +8 -0
  48. ccproxy/cli/commands/auth.py +553 -0
  49. ccproxy/cli/commands/config/__init__.py +14 -0
  50. ccproxy/cli/commands/config/commands.py +766 -0
  51. ccproxy/cli/commands/config/schema_commands.py +119 -0
  52. ccproxy/cli/commands/serve.py +630 -0
  53. ccproxy/cli/docker/__init__.py +34 -0
  54. ccproxy/cli/docker/adapter_factory.py +157 -0
  55. ccproxy/cli/docker/params.py +278 -0
  56. ccproxy/cli/helpers.py +144 -0
  57. ccproxy/cli/main.py +193 -0
  58. ccproxy/cli/options/__init__.py +14 -0
  59. ccproxy/cli/options/claude_options.py +216 -0
  60. ccproxy/cli/options/core_options.py +40 -0
  61. ccproxy/cli/options/security_options.py +48 -0
  62. ccproxy/cli/options/server_options.py +117 -0
  63. ccproxy/config/__init__.py +40 -0
  64. ccproxy/config/auth.py +154 -0
  65. ccproxy/config/claude.py +124 -0
  66. ccproxy/config/cors.py +79 -0
  67. ccproxy/config/discovery.py +87 -0
  68. ccproxy/config/docker_settings.py +265 -0
  69. ccproxy/config/loader.py +108 -0
  70. ccproxy/config/observability.py +158 -0
  71. ccproxy/config/pricing.py +88 -0
  72. ccproxy/config/reverse_proxy.py +31 -0
  73. ccproxy/config/scheduler.py +89 -0
  74. ccproxy/config/security.py +14 -0
  75. ccproxy/config/server.py +81 -0
  76. ccproxy/config/settings.py +534 -0
  77. ccproxy/config/validators.py +231 -0
  78. ccproxy/core/__init__.py +274 -0
  79. ccproxy/core/async_utils.py +675 -0
  80. ccproxy/core/constants.py +97 -0
  81. ccproxy/core/errors.py +256 -0
  82. ccproxy/core/http.py +328 -0
  83. ccproxy/core/http_transformers.py +428 -0
  84. ccproxy/core/interfaces.py +247 -0
  85. ccproxy/core/logging.py +189 -0
  86. ccproxy/core/middleware.py +114 -0
  87. ccproxy/core/proxy.py +143 -0
  88. ccproxy/core/system.py +38 -0
  89. ccproxy/core/transformers.py +259 -0
  90. ccproxy/core/types.py +129 -0
  91. ccproxy/core/validators.py +288 -0
  92. ccproxy/docker/__init__.py +67 -0
  93. ccproxy/docker/adapter.py +588 -0
  94. ccproxy/docker/docker_path.py +207 -0
  95. ccproxy/docker/middleware.py +103 -0
  96. ccproxy/docker/models.py +228 -0
  97. ccproxy/docker/protocol.py +192 -0
  98. ccproxy/docker/stream_process.py +264 -0
  99. ccproxy/docker/validators.py +173 -0
  100. ccproxy/models/__init__.py +123 -0
  101. ccproxy/models/errors.py +42 -0
  102. ccproxy/models/messages.py +243 -0
  103. ccproxy/models/requests.py +85 -0
  104. ccproxy/models/responses.py +227 -0
  105. ccproxy/models/types.py +102 -0
  106. ccproxy/observability/__init__.py +51 -0
  107. ccproxy/observability/access_logger.py +400 -0
  108. ccproxy/observability/context.py +447 -0
  109. ccproxy/observability/metrics.py +539 -0
  110. ccproxy/observability/pushgateway.py +366 -0
  111. ccproxy/observability/sse_events.py +303 -0
  112. ccproxy/observability/stats_printer.py +755 -0
  113. ccproxy/observability/storage/__init__.py +1 -0
  114. ccproxy/observability/storage/duckdb_simple.py +665 -0
  115. ccproxy/observability/storage/models.py +55 -0
  116. ccproxy/pricing/__init__.py +19 -0
  117. ccproxy/pricing/cache.py +212 -0
  118. ccproxy/pricing/loader.py +267 -0
  119. ccproxy/pricing/models.py +106 -0
  120. ccproxy/pricing/updater.py +309 -0
  121. ccproxy/scheduler/__init__.py +39 -0
  122. ccproxy/scheduler/core.py +335 -0
  123. ccproxy/scheduler/exceptions.py +34 -0
  124. ccproxy/scheduler/manager.py +186 -0
  125. ccproxy/scheduler/registry.py +150 -0
  126. ccproxy/scheduler/tasks.py +484 -0
  127. ccproxy/services/__init__.py +10 -0
  128. ccproxy/services/claude_sdk_service.py +614 -0
  129. ccproxy/services/credentials/__init__.py +55 -0
  130. ccproxy/services/credentials/config.py +105 -0
  131. ccproxy/services/credentials/manager.py +562 -0
  132. ccproxy/services/credentials/oauth_client.py +482 -0
  133. ccproxy/services/proxy_service.py +1536 -0
  134. ccproxy/static/.keep +0 -0
  135. ccproxy/testing/__init__.py +34 -0
  136. ccproxy/testing/config.py +148 -0
  137. ccproxy/testing/content_generation.py +197 -0
  138. ccproxy/testing/mock_responses.py +262 -0
  139. ccproxy/testing/response_handlers.py +161 -0
  140. ccproxy/testing/scenarios.py +241 -0
  141. ccproxy/utils/__init__.py +6 -0
  142. ccproxy/utils/cost_calculator.py +210 -0
  143. ccproxy/utils/streaming_metrics.py +199 -0
  144. ccproxy_api-0.1.0.dist-info/METADATA +253 -0
  145. ccproxy_api-0.1.0.dist-info/RECORD +148 -0
  146. ccproxy_api-0.1.0.dist-info/WHEEL +4 -0
  147. ccproxy_api-0.1.0.dist-info/entry_points.txt +2 -0
  148. ccproxy_api-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,227 @@
1
+ """Response models for Claude Proxy API Server compatible with Anthropic's API format."""
2
+
3
+ from typing import Annotated, Any, Literal
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from .requests import MessageContent, Usage
8
+
9
+
10
+ class ToolCall(BaseModel):
11
+ """Tool call made by the model."""
12
+
13
+ id: Annotated[str, Field(description="Unique identifier for the tool call")]
14
+ type: Annotated[Literal["function"], Field(description="Tool call type")] = (
15
+ "function"
16
+ )
17
+ function: Annotated[
18
+ dict[str, Any],
19
+ Field(description="Function call details including name and arguments"),
20
+ ]
21
+
22
+
23
+ class ToolUse(BaseModel):
24
+ """Tool use content block."""
25
+
26
+ type: Annotated[Literal["tool_use"], Field(description="Content type")] = "tool_use"
27
+ id: Annotated[str, Field(description="Unique identifier for the tool use")]
28
+ name: Annotated[str, Field(description="Name of the tool being used")]
29
+ input: Annotated[dict[str, Any], Field(description="Input parameters for the tool")]
30
+
31
+
32
+ class TextResponse(BaseModel):
33
+ """Text response content block."""
34
+
35
+ type: Annotated[Literal["text"], Field(description="Content type")] = "text"
36
+ text: Annotated[str, Field(description="The generated text content")]
37
+
38
+
39
+ ResponseContent = TextResponse | ToolUse
40
+
41
+
42
+ class Choice(BaseModel):
43
+ """Individual choice in a non-streaming response."""
44
+
45
+ index: Annotated[int, Field(description="Index of the choice")]
46
+ message: Annotated[dict[str, Any], Field(description="The generated message")]
47
+ finish_reason: Annotated[
48
+ str | None, Field(description="Reason why the model stopped generating")
49
+ ] = None
50
+
51
+ model_config = ConfigDict(extra="forbid")
52
+
53
+
54
+ class StreamingChoice(BaseModel):
55
+ """Individual choice in a streaming response."""
56
+
57
+ index: Annotated[int, Field(description="Index of the choice")]
58
+ delta: Annotated[
59
+ dict[str, Any], Field(description="The incremental message content")
60
+ ]
61
+ finish_reason: Annotated[
62
+ str | None, Field(description="Reason why the model stopped generating")
63
+ ] = None
64
+
65
+ model_config = ConfigDict(extra="forbid")
66
+
67
+
68
+ class ChatCompletionResponse(BaseModel):
69
+ """Response model for Claude chat completions compatible with Anthropic's API."""
70
+
71
+ id: Annotated[str, Field(description="Unique identifier for the response")]
72
+ type: Annotated[Literal["message"], Field(description="Response type")] = "message"
73
+ role: Annotated[Literal["assistant"], Field(description="Message role")] = (
74
+ "assistant"
75
+ )
76
+ content: Annotated[
77
+ list[ResponseContent],
78
+ Field(description="Array of content blocks in the response"),
79
+ ]
80
+ model: Annotated[str, Field(description="The model used for the response")]
81
+ stop_reason: Annotated[
82
+ str | None, Field(description="Reason why the model stopped generating")
83
+ ] = None
84
+ stop_sequence: Annotated[
85
+ str | None,
86
+ Field(description="The stop sequence that triggered stopping (if applicable)"),
87
+ ] = None
88
+ usage: Annotated[Usage, Field(description="Token usage information")]
89
+
90
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
91
+
92
+
93
+ class StreamingChatCompletionResponse(BaseModel):
94
+ """Streaming response model for Claude chat completions."""
95
+
96
+ id: Annotated[str, Field(description="Unique identifier for the response")]
97
+ type: Annotated[
98
+ Literal[
99
+ "message_start",
100
+ "message_delta",
101
+ "message_stop",
102
+ "content_block_start",
103
+ "content_block_delta",
104
+ "content_block_stop",
105
+ "ping",
106
+ ],
107
+ Field(description="Type of streaming event"),
108
+ ]
109
+ message: Annotated[
110
+ dict[str, Any] | None, Field(description="Message data for message events")
111
+ ] = None
112
+ index: Annotated[int | None, Field(description="Index of the content block")] = None
113
+ content_block: Annotated[
114
+ dict[str, Any] | None, Field(description="Content block data")
115
+ ] = None
116
+ delta: Annotated[
117
+ dict[str, Any] | None, Field(description="Delta data for incremental updates")
118
+ ] = None
119
+ usage: Annotated[Usage | None, Field(description="Token usage information")] = None
120
+
121
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
122
+
123
+
124
+ class ErrorResponse(BaseModel):
125
+ """Error response model."""
126
+
127
+ type: Annotated[Literal["error"], Field(description="Response type")] = "error"
128
+ error: Annotated[
129
+ dict[str, Any], Field(description="Error details including type and message")
130
+ ]
131
+
132
+ model_config = ConfigDict(extra="forbid")
133
+
134
+
135
+ class APIError(BaseModel):
136
+ """API error details."""
137
+
138
+ type: Annotated[str, Field(description="Error type")]
139
+ message: Annotated[str, Field(description="Error message")]
140
+
141
+ model_config = ConfigDict(
142
+ extra="forbid", validate_by_alias=True, validate_by_name=True
143
+ )
144
+
145
+
146
+ class PermissionToolAllowResponse(BaseModel):
147
+ """Response model for allowed permission tool requests."""
148
+
149
+ behavior: Annotated[Literal["allow"], Field(description="Permission behavior")] = (
150
+ "allow"
151
+ )
152
+ updated_input: Annotated[
153
+ dict[str, Any],
154
+ Field(
155
+ description="Updated input parameters for the tool, or original input if unchanged",
156
+ alias="updatedInput",
157
+ ),
158
+ ]
159
+
160
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
161
+
162
+
163
+ class PermissionToolDenyResponse(BaseModel):
164
+ """Response model for denied permission tool requests."""
165
+
166
+ behavior: Annotated[Literal["deny"], Field(description="Permission behavior")] = (
167
+ "deny"
168
+ )
169
+ message: Annotated[
170
+ str,
171
+ Field(
172
+ description="Human-readable explanation of why the permission was denied"
173
+ ),
174
+ ]
175
+
176
+ model_config = ConfigDict(extra="forbid")
177
+
178
+
179
+ PermissionToolResponse = PermissionToolAllowResponse | PermissionToolDenyResponse
180
+
181
+
182
+ class RateLimitError(APIError):
183
+ """Rate limit error."""
184
+
185
+ type: Annotated[Literal["rate_limit_error"], Field(description="Error type")] = (
186
+ "rate_limit_error"
187
+ )
188
+
189
+
190
+ class InvalidRequestError(APIError):
191
+ """Invalid request error."""
192
+
193
+ type: Annotated[
194
+ Literal["invalid_request_error"], Field(description="Error type")
195
+ ] = "invalid_request_error"
196
+
197
+
198
+ class AuthenticationError(APIError):
199
+ """Authentication error."""
200
+
201
+ type: Annotated[
202
+ Literal["authentication_error"], Field(description="Error type")
203
+ ] = "authentication_error"
204
+
205
+
206
+ class NotFoundError(APIError):
207
+ """Not found error."""
208
+
209
+ type: Annotated[Literal["not_found_error"], Field(description="Error type")] = (
210
+ "not_found_error"
211
+ )
212
+
213
+
214
+ class OverloadedError(APIError):
215
+ """Overloaded error."""
216
+
217
+ type: Annotated[Literal["overloaded_error"], Field(description="Error type")] = (
218
+ "overloaded_error"
219
+ )
220
+
221
+
222
+ class InternalServerError(APIError):
223
+ """Internal server error."""
224
+
225
+ type: Annotated[
226
+ Literal["internal_server_error"], Field(description="Error type")
227
+ ] = "internal_server_error"
@@ -0,0 +1,102 @@
1
+ """Common type aliases used across the ccproxy models."""
2
+
3
+ from typing import Literal, TypeAlias
4
+
5
+ from typing_extensions import TypedDict
6
+
7
+
8
+ # Message and content types
9
+ MessageRole: TypeAlias = Literal["user", "assistant", "system", "tool"]
10
+ OpenAIMessageRole: TypeAlias = Literal[
11
+ "system", "user", "assistant", "tool", "developer"
12
+ ]
13
+ ContentBlockType: TypeAlias = Literal[
14
+ "text", "image", "image_url", "tool_use", "thinking"
15
+ ]
16
+ OpenAIContentType: TypeAlias = Literal["text", "image_url"]
17
+
18
+ # Tool-related types
19
+ ToolChoiceType: TypeAlias = Literal["auto", "any", "tool", "none", "required"]
20
+ OpenAIToolChoiceType: TypeAlias = Literal["none", "auto", "required"]
21
+ ToolType: TypeAlias = Literal["function", "custom"]
22
+
23
+ # Response format types
24
+ ResponseFormatType: TypeAlias = Literal["text", "json_object", "json_schema"]
25
+
26
+ # Service tier types
27
+ ServiceTier: TypeAlias = Literal["auto", "standard_only"]
28
+
29
+ # Stop reasons (re-exported from messages for convenience)
30
+ StopReason: TypeAlias = Literal[
31
+ "end_turn",
32
+ "max_tokens",
33
+ "stop_sequence",
34
+ "tool_use",
35
+ "pause_turn",
36
+ "refusal",
37
+ ]
38
+
39
+ # OpenAI finish reasons
40
+ OpenAIFinishReason: TypeAlias = Literal[
41
+ "stop", "length", "tool_calls", "content_filter"
42
+ ]
43
+
44
+ # Error types
45
+ ErrorType: TypeAlias = Literal[
46
+ "error",
47
+ "rate_limit_error",
48
+ "invalid_request_error",
49
+ "authentication_error",
50
+ "not_found_error",
51
+ "overloaded_error",
52
+ "internal_server_error",
53
+ ]
54
+
55
+ # Stream event types
56
+ StreamEventType: TypeAlias = Literal[
57
+ "message_start",
58
+ "message_delta",
59
+ "message_stop",
60
+ "content_block_start",
61
+ "content_block_delta",
62
+ "content_block_stop",
63
+ "ping",
64
+ ]
65
+
66
+ # Image source types
67
+ ImageSourceType: TypeAlias = Literal["base64", "url"]
68
+
69
+ # Modality types
70
+ ModalityType: TypeAlias = Literal["text", "audio"]
71
+
72
+ # Reasoning effort types (OpenAI o1 models)
73
+ ReasoningEffort: TypeAlias = Literal["low", "medium", "high"]
74
+
75
+ # OpenAI object types
76
+ OpenAIObjectType: TypeAlias = Literal[
77
+ "chat.completion", "chat.completion.chunk", "model", "list"
78
+ ]
79
+
80
+ # Permission behavior types
81
+ PermissionBehavior: TypeAlias = Literal["allow", "deny"]
82
+
83
+
84
+ # Usage and streaming related types
85
+ class UsageData(TypedDict, total=False):
86
+ """Token usage data extracted from streaming or non-streaming responses."""
87
+
88
+ input_tokens: int | None
89
+ output_tokens: int | None
90
+ cache_read_input_tokens: int | None
91
+ cache_creation_input_tokens: int | None
92
+ event_type: StreamEventType | None
93
+
94
+
95
+ class StreamingTokenMetrics(TypedDict, total=False):
96
+ """Accumulated token metrics during streaming."""
97
+
98
+ tokens_input: int | None
99
+ tokens_output: int | None
100
+ cache_read_tokens: int | None
101
+ cache_write_tokens: int | None
102
+ cost_usd: float | None
@@ -0,0 +1,51 @@
1
+ """
2
+ Observability module for the CCProxy API.
3
+
4
+ This module provides comprehensive observability capabilities including metrics collection,
5
+ structured logging, request context tracking, and observability pipeline management.
6
+
7
+ The observability system follows a hybrid architecture that combines:
8
+ - Real-time metrics collection and aggregation
9
+ - Structured logging with correlation IDs
10
+ - Request context propagation across service boundaries
11
+ - Pluggable pipeline for metrics export and alerting
12
+
13
+ Components:
14
+ - metrics: Core metrics collection, aggregation, and export functionality
15
+ - logging: Structured logging configuration and context-aware loggers
16
+ - context: Request context tracking and correlation across async operations
17
+ - pipeline: Observability data pipeline for metrics export and alerting
18
+ """
19
+
20
+ from .context import (
21
+ RequestContext,
22
+ get_context_tracker,
23
+ request_context,
24
+ timed_operation,
25
+ tracked_request_context,
26
+ )
27
+ from .metrics import PrometheusMetrics, get_metrics, reset_metrics
28
+ from .pushgateway import (
29
+ PushgatewayClient,
30
+ get_pushgateway_client,
31
+ reset_pushgateway_client,
32
+ )
33
+
34
+
35
+ __all__ = [
36
+ # Configuration
37
+ # Context management
38
+ "RequestContext",
39
+ "request_context",
40
+ "tracked_request_context",
41
+ "timed_operation",
42
+ "get_context_tracker",
43
+ # Prometheus metrics
44
+ "PrometheusMetrics",
45
+ "get_metrics",
46
+ "reset_metrics",
47
+ # Pushgateway
48
+ "PushgatewayClient",
49
+ "get_pushgateway_client",
50
+ "reset_pushgateway_client",
51
+ ]