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,199 @@
1
+ """Streaming metrics extraction utilities.
2
+
3
+ This module provides utilities for extracting token usage and calculating costs
4
+ from Anthropic streaming responses in a testable, modular way.
5
+ """
6
+
7
+ import json
8
+ from typing import Any
9
+
10
+ import structlog
11
+
12
+ from ccproxy.models.types import StreamingTokenMetrics, UsageData
13
+ from ccproxy.utils.cost_calculator import calculate_token_cost
14
+
15
+
16
+ logger = structlog.get_logger(__name__)
17
+
18
+
19
+ def extract_usage_from_streaming_chunk(chunk_data: Any) -> UsageData | None:
20
+ """Extract usage information from Anthropic streaming response chunk.
21
+
22
+ This function looks for usage information in both message_start and message_delta events
23
+ from Anthropic's streaming API responses. message_start contains initial input tokens,
24
+ message_delta contains final output tokens.
25
+
26
+ Args:
27
+ chunk_data: Streaming response chunk dictionary
28
+
29
+ Returns:
30
+ UsageData with token counts or None if no usage found
31
+ """
32
+ if not isinstance(chunk_data, dict):
33
+ return None
34
+
35
+ chunk_type = chunk_data.get("type")
36
+
37
+ # Look for message_start events with initial usage (input tokens)
38
+ if chunk_type == "message_start" and "message" in chunk_data:
39
+ message = chunk_data["message"]
40
+ if "usage" in message:
41
+ usage = message["usage"]
42
+ return UsageData(
43
+ input_tokens=usage.get("input_tokens"),
44
+ output_tokens=usage.get(
45
+ "output_tokens"
46
+ ), # Initial output tokens (usually small)
47
+ cache_read_input_tokens=usage.get("cache_read_input_tokens"),
48
+ cache_creation_input_tokens=usage.get("cache_creation_input_tokens"),
49
+ event_type="message_start",
50
+ )
51
+
52
+ # Look for message_delta events with final usage (output tokens)
53
+ elif chunk_type == "message_delta" and "usage" in chunk_data:
54
+ usage = chunk_data["usage"]
55
+ return UsageData(
56
+ input_tokens=usage.get("input_tokens"), # Usually None in delta
57
+ output_tokens=usage.get("output_tokens"), # Final output token count
58
+ cache_read_input_tokens=usage.get("cache_read_input_tokens"),
59
+ cache_creation_input_tokens=usage.get("cache_creation_input_tokens"),
60
+ event_type="message_delta",
61
+ )
62
+
63
+ return None
64
+
65
+
66
+ class StreamingMetricsCollector:
67
+ """Collects and manages token metrics during streaming responses."""
68
+
69
+ def __init__(self, request_id: str | None = None) -> None:
70
+ """Initialize the metrics collector.
71
+
72
+ Args:
73
+ request_id: Optional request ID for logging context
74
+ """
75
+ self.request_id = request_id
76
+ self.metrics = StreamingTokenMetrics(
77
+ tokens_input=None,
78
+ tokens_output=None,
79
+ cache_read_tokens=None,
80
+ cache_write_tokens=None,
81
+ cost_usd=None,
82
+ )
83
+
84
+ def process_chunk(self, chunk_str: str) -> bool:
85
+ """Process a streaming chunk to extract token metrics.
86
+
87
+ Args:
88
+ chunk_str: Raw chunk string from streaming response
89
+
90
+ Returns:
91
+ True if this was the final chunk with complete metrics, False otherwise
92
+ """
93
+ # Check if this chunk contains usage information
94
+ # Look for usage data in any chunk - the event type will be determined from the JSON
95
+ if "usage" not in chunk_str:
96
+ return False
97
+
98
+ logger.debug(
99
+ "Processing chunk with usage",
100
+ chunk_preview=chunk_str[:300],
101
+ request_id=self.request_id,
102
+ )
103
+
104
+ try:
105
+ # Parse SSE data lines to find usage information
106
+ for line in chunk_str.split("\n"):
107
+ if line.startswith("data: "):
108
+ data_str = line[6:].strip()
109
+ if data_str and data_str != "[DONE]":
110
+ event_data = json.loads(data_str)
111
+ usage_data = extract_usage_from_streaming_chunk(event_data)
112
+
113
+ if usage_data:
114
+ event_type = usage_data.get("event_type")
115
+
116
+ # Handle message_start: get input tokens and initial cache tokens
117
+ if event_type == "message_start":
118
+ self.metrics["tokens_input"] = usage_data.get(
119
+ "input_tokens"
120
+ )
121
+ self.metrics["cache_read_tokens"] = (
122
+ usage_data.get("cache_read_input_tokens")
123
+ or self.metrics["cache_read_tokens"]
124
+ )
125
+ self.metrics["cache_write_tokens"] = (
126
+ usage_data.get("cache_creation_input_tokens")
127
+ or self.metrics["cache_write_tokens"]
128
+ )
129
+ logger.debug(
130
+ "Extracted input tokens from message_start",
131
+ tokens_input=self.metrics["tokens_input"],
132
+ cache_read_tokens=self.metrics["cache_read_tokens"],
133
+ cache_write_tokens=self.metrics[
134
+ "cache_write_tokens"
135
+ ],
136
+ request_id=self.request_id,
137
+ )
138
+ return False # Not final yet
139
+
140
+ # Handle message_delta: get final output tokens
141
+ elif event_type == "message_delta":
142
+ self.metrics["tokens_output"] = usage_data.get(
143
+ "output_tokens"
144
+ )
145
+ logger.debug(
146
+ "Extracted output tokens from message_delta",
147
+ tokens_output=self.metrics["tokens_output"],
148
+ request_id=self.request_id,
149
+ )
150
+ return True # This is the final event
151
+
152
+ break # Only process first valid data line
153
+
154
+ except (json.JSONDecodeError, KeyError) as e:
155
+ logger.debug(
156
+ "Failed to parse streaming token metrics",
157
+ error=str(e),
158
+ request_id=self.request_id,
159
+ )
160
+
161
+ return False
162
+
163
+ def calculate_final_cost(self, model: str | None) -> float | None:
164
+ """Calculate the final cost based on collected metrics.
165
+
166
+ Args:
167
+ model: Model name for pricing lookup
168
+
169
+ Returns:
170
+ Final cost in USD or None if calculation fails
171
+ """
172
+ cost_usd = calculate_token_cost(
173
+ self.metrics["tokens_input"],
174
+ self.metrics["tokens_output"],
175
+ model,
176
+ self.metrics["cache_read_tokens"],
177
+ self.metrics["cache_write_tokens"],
178
+ )
179
+ self.metrics["cost_usd"] = cost_usd
180
+
181
+ logger.debug(
182
+ "Final streaming token metrics",
183
+ tokens_input=self.metrics["tokens_input"],
184
+ tokens_output=self.metrics["tokens_output"],
185
+ cache_read_tokens=self.metrics["cache_read_tokens"],
186
+ cache_write_tokens=self.metrics["cache_write_tokens"],
187
+ cost_usd=cost_usd,
188
+ request_id=self.request_id,
189
+ )
190
+
191
+ return cost_usd
192
+
193
+ def get_metrics(self) -> StreamingTokenMetrics:
194
+ """Get the current collected metrics.
195
+
196
+ Returns:
197
+ Current token metrics
198
+ """
199
+ return self.metrics.copy()
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccproxy-api
3
+ Version: 0.1.0
4
+ Summary: API server that provides an Anthropic and OpenAI compatible interface over Claude Code, allowing to use your Claude OAuth account or over the API.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiosqlite>=0.21.0
8
+ Requires-Dist: claude-code-sdk>=0.0.14
9
+ Requires-Dist: duckdb-engine>=0.17.0
10
+ Requires-Dist: duckdb>=1.1.0
11
+ Requires-Dist: fastapi[standard]>=0.115.14
12
+ Requires-Dist: httpx-sse>=0.4.1
13
+ Requires-Dist: httpx>=0.28.1
14
+ Requires-Dist: jsonschema>=0.33.2
15
+ Requires-Dist: keyring>=25.6.0
16
+ Requires-Dist: openai>=1.93.0
17
+ Requires-Dist: prometheus-client>=0.22.1
18
+ Requires-Dist: pydantic-settings>=2.4.0
19
+ Requires-Dist: pydantic>=2.8.0
20
+ Requires-Dist: rich-toolkit>=0.14.8
21
+ Requires-Dist: rich>=13.0.0
22
+ Requires-Dist: sqlmodel>=0.0.24
23
+ Requires-Dist: structlog>=25.4.0
24
+ Requires-Dist: tomli>=2.0.0; python_version < '3.11'
25
+ Requires-Dist: typer>=0.16.0
26
+ Requires-Dist: typing-extensions>=4.0.0
27
+ Requires-Dist: uvicorn>=0.34.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # CCProxy API Server
31
+
32
+ `ccproxy` is a local reverse proxy server for Anthropic Claude LLM at `api.anthropic.com/v1/messages`. It allows you to use your existing Claude Max subscription to interact with the Anthropic API, bypassing the need for separate API key billing.
33
+
34
+ The server provides two primary modes of operation:
35
+ * **SDK Mode (`/sdk`):** Routes requests through the local `claude-code-sdk`. This enables access to tools configured in your Claude environment.
36
+ * **API Mode (`/api`):** Acts as a direct reverse proxy, injecting the necessary authentication headers. This provides full access to the underlying API features and model settings.
37
+
38
+ It includes a translation layer to support both Anthropic and OpenAI-compatible API formats for requests and responses, including streaming.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ # The official claude-code CLI is required for SDK mode
44
+ npm install -g @anthropic-ai/claude-code
45
+
46
+ # Install ccproxy
47
+ pipx install git+https://github.com/caddyglow/ccproxy-api.git@dev
48
+
49
+ # Optional: Enable shell completion
50
+ eval "$(ccproxy --show-completion zsh)" # For zsh
51
+ eval "$(ccproxy --show-completion bash)" # For bash
52
+ ```
53
+
54
+ ## Authentication
55
+
56
+ The proxy uses two different authentication mechanisms depending on the mode.
57
+
58
+ 1. **Claude CLI (`sdk` mode):**
59
+ This mode relies on the authentication handled by the `claude-code-sdk`.
60
+ ```bash
61
+ claude /login
62
+ ```
63
+
64
+ 2. **ccproxy (`api` mode):**
65
+ This mode uses its own OAuth2 flow to obtain credentials for direct API access.
66
+ ```bash
67
+ ccproxy auth login
68
+ ```
69
+ You can check the status of these credentials with `ccproxy auth validate` and `ccproxy auth info`.
70
+
71
+ ## Usage
72
+
73
+ ### Running the Server
74
+
75
+ ```bash
76
+ # Start the proxy server
77
+ ccproxy
78
+ ```
79
+ The server will start on `http://127.0.0.1:8000` by default.
80
+
81
+ ### Client Configuration
82
+
83
+ Point your existing tools and applications to the local proxy instance by setting the appropriate environment variables. A dummy API key is required by most client libraries but is not used by the proxy itself.
84
+
85
+ **For OpenAI-compatible clients:**
86
+ ```bash
87
+ # For SDK mode
88
+ export OPENAI_BASE_URL="http://localhost:8000/sdk/v1"
89
+ # For API mode
90
+ export OPENAI_BASE_URL="http://localhost:8000/api/v1"
91
+
92
+ export OPENAI_API_KEY="dummy-key"
93
+ ```
94
+
95
+ **For Anthropic-compatible clients:**
96
+ ```bash
97
+ # For SDK mode
98
+ export ANTHROPIC_BASE_URL="http://localhost:8000/sdk"
99
+ # For API mode
100
+ export ANTHROPIC_BASE_URL="http://localhost:8000/api"
101
+
102
+ export ANTHROPIC_API_KEY="dummy-key"
103
+ ```
104
+
105
+ ### `curl` Example
106
+
107
+ ```bash
108
+ # SDK mode
109
+ curl -X POST http://localhost:8000/sdk/v1/messages \
110
+ -H "Content-Type: application/json" \
111
+ -d '{
112
+ "model": "claude-3-5-sonnet-20241022",
113
+ "messages": [{"role": "user", "content": "Hello!"}],
114
+ "max_tokens": 100
115
+ }'
116
+
117
+ # API mode
118
+ curl -X POST http://localhost:8000/api/v1/messages \
119
+ -H "Content-Type: application/json" \
120
+ -d '{
121
+ "model": "claude-3-5-sonnet-20241022",
122
+ "messages": [{"role": "user", "content": "Hello!"}],
123
+ "max_tokens": 100
124
+ }'
125
+ ```
126
+ More examples are available in the `examples/` directory.
127
+
128
+ ## Endpoints
129
+
130
+ The proxy exposes endpoints under two prefixes, corresponding to its operating modes.
131
+
132
+ | Mode | URL Prefix | Description | Use Case |
133
+ |------|------------|-------------|----------|
134
+ | **SDK** | `/sdk/` | Uses `claude-code-sdk` with its configured tools. | Accessing Claude with local tools. |
135
+ | **API** | `/api/` | Direct proxy with header injection. | Full API control, direct access. |
136
+
137
+ * **Anthropic:**
138
+ * `POST /sdk/v1/messages`
139
+ * `POST /api/v1/messages`
140
+ * **OpenAI-Compatible:**
141
+ * `POST /sdk/v1/chat/completions`
142
+ * `POST /api/v1/chat/completions`
143
+ * **Utility:**
144
+ * `GET /health`
145
+ * `GET /sdk/models`, `GET /api/models`
146
+ * `GET /sdk/status`, `GET /api/status`
147
+ * `GET /oauth/callback`
148
+ * **Observability (Optional):**
149
+ * `GET /metrics`
150
+ * `GET /logs/status`, `GET /logs/query`
151
+ * `GET /dashboard`
152
+
153
+ ## Supported Models
154
+
155
+ CCProxy supports recent Claude models including Opus, Sonnet, and Haiku variants. The specific models available to you will depend on your Claude account and the features enabled for your subscription.
156
+
157
+ * `claude-opus-4-20250514`
158
+ * `claude-sonnet-4-20250514`
159
+ * `claude-3-7-sonnet-20250219`
160
+ * `claude-3-5-sonnet-20241022`
161
+ * `claude-3-5-sonnet-20240620`
162
+
163
+ ## Configuration
164
+
165
+ Settings can be configured through (in order of precedence):
166
+ 1. Command-line arguments
167
+ 2. Environment variables
168
+ 3. `.env` file
169
+ 4. TOML configuration files (`.ccproxy.toml`, `ccproxy.toml`, or `~/.config/ccproxy/config.toml`)
170
+ 5. Default values
171
+
172
+ For complex configurations, you can use a nested syntax for environment variables with `__` as a delimiter:
173
+
174
+ ```bash
175
+ # Server settings
176
+ SERVER__HOST=0.0.0.0
177
+ SERVER__PORT=8080
178
+ # etc.
179
+ ```
180
+
181
+ ## Securing the Proxy (Optional)
182
+
183
+ You can enable token authentication for the proxy. This supports multiple header formats (`x-api-key` for Anthropic, `Authorization: Bearer` for OpenAI) for compatibility with standard client libraries.
184
+
185
+ **1. Generate a Token:**
186
+ ```bash
187
+ ccproxy generate-token
188
+ # Output: AUTH_TOKEN=abc123xyz789...
189
+ ```
190
+
191
+ **2. Configure the Token:**
192
+ ```bash
193
+ # Set environment variable
194
+ export AUTH_TOKEN=abc123xyz789...
195
+
196
+ # Or add to .env file
197
+ echo "AUTH_TOKEN=abc123xyz789..." >> .env
198
+ ```
199
+
200
+ **3. Use in Requests:**
201
+ When authentication is enabled, include the token in your API requests.
202
+ ```bash
203
+ # Anthropic Format (x-api-key)
204
+ curl -H "x-api-key: your-token" ...
205
+
206
+ # OpenAI/Bearer Format
207
+ curl -H "Authorization: Bearer your-token" ...
208
+ ```
209
+
210
+ ## Observability
211
+
212
+ `ccproxy` includes an optional but powerful observability suite for monitoring and analytics. When enabled, it provides:
213
+
214
+ * **Prometheus Metrics:** A `/metrics` endpoint for real-time operational monitoring.
215
+ * **Access Log Storage:** Detailed request logs, including token usage and costs, are stored in a local DuckDB database.
216
+ * **Analytics API:** Endpoints to query and analyze historical usage data.
217
+ * **Real-time Dashboard:** A live web interface at `/dashboard` to visualize metrics and request streams.
218
+
219
+ These features are disabled by default and can be enabled via configuration. For a complete guide on setting up and using these features, see the [Observability Documentation](docs/observability.md).
220
+
221
+ ## Troubleshooting
222
+
223
+ ### Common Issues
224
+
225
+ 1. **Authentication Error:** Ensure you're using the correct mode (`/sdk` or `/api`) for your authentication method.
226
+ 2. **Claude Credentials Expired:** Run `ccproxy auth login` to refresh credentials for API mode. Run `claude /login` for SDK mode.
227
+ 3. **Missing API Auth Token:** If you've enabled security, include the token in your request headers.
228
+ 4. **Port Already in Use:** Start the server on a different port: `ccproxy --port 8001`.
229
+ 5. **Model Not Available:** Check that your Claude subscription includes the requested model.
230
+
231
+ ## Contributing
232
+
233
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
234
+
235
+ ## License
236
+
237
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
238
+
239
+ ## Documentation
240
+
241
+ - **[Online Documentation](https://caddyglow.github.io/ccproxy-api)**
242
+ - **[API Reference](https://caddyglow.github.io/ccproxy-api/api-reference/overview/)**
243
+ - **[Developer Guide](https://caddyglow.github.io/ccproxy-api/developer-guide/architecture/)**
244
+
245
+ ## Support
246
+
247
+ - Issues: [GitHub Issues](https://github.com/CaddyGlow/ccproxy-api/issues)
248
+ - Documentation: [Project Documentation](https://caddyglow.github.io/ccproxy-api)
249
+
250
+ ## Acknowledgments
251
+
252
+ - [Anthropic](https://anthropic.com) for Claude and the Claude Code SDK
253
+ - The open-source community
@@ -0,0 +1,148 @@
1
+ ccproxy/__init__.py,sha256=VrtzUTUA8MwpYqFJAiwKLPxRdMRc7UOzFi2VB_1W9qw,62
2
+ ccproxy/__main__.py,sha256=kcejcfzAaMnFiSxEiVtNl-_TwynpntkupRxqG5XR15s,116
3
+ ccproxy/_version.py,sha256=-LyU5F1uZDjn6Q8_Z6-_FJt_8RE4Kq9zcKdg1abSSps,511
4
+ ccproxy/adapters/__init__.py,sha256=CMr5MPIFigfazoXfhyD2eLqBrutzaSzBaEi8u2i9xJQ,206
5
+ ccproxy/adapters/base.py,sha256=aufx8ho9LhF0kmTsCvw1a9K3lk5YyYymJV8h_wt5TpU,2191
6
+ ccproxy/adapters/openai/__init__.py,sha256=ZBT4y3cHZHrhzsxETOJNDePDT-2rfWwsPOzDKDRZAvY,1125
7
+ ccproxy/adapters/openai/adapter.py,sha256=PuN8YY-T0vk6eSxDwt_oG7TCcoA9odgQHPqFy_Er2dk,35747
8
+ ccproxy/adapters/openai/models.py,sha256=tg5NLFmTAOjLd2-gjqRQPU3IkL9ATftXv4ZugGw-rwc,11645
9
+ ccproxy/adapters/openai/streaming.py,sha256=y3GEGWpd_3Pyg4mclR5RB8xWMq7sYj1T4qx8eOLcJTU,15670
10
+ ccproxy/api/__init__.py,sha256=m3t0z3_agXKDupMjmdFW1pu2_YrOTu6DKi0b1OdR-YM,558
11
+ ccproxy/api/app.py,sha256=Yx1w-KgBaZDKJ2UYVlJVmYqfSrmOwnWpK2CM14gY4sU,7937
12
+ ccproxy/api/dependencies.py,sha256=VRYA3A1Az1qglOT55anGr5WdHq4ZapdcqHK5fAMk9oo,4065
13
+ ccproxy/api/responses.py,sha256=eipdbHLQcrUjBYJd-Q5bEWUoiFlmPiDB0wdySFNubx4,2905
14
+ ccproxy/api/middleware/__init__.py,sha256=S887PXY40Tnb0IFGei4Sgs7sygkcoT0IEOciOO7IDBc,284
15
+ ccproxy/api/middleware/auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ ccproxy/api/middleware/cors.py,sha256=u8bnWmBXRG9J2etbQdNsQOTIQY6PFWawk2Oa0Ei1x3s,1666
17
+ ccproxy/api/middleware/errors.py,sha256=XMpJui7i_OFGfymQTEPx-YqZ98M1W5VrO7fxdemSGMY,21929
18
+ ccproxy/api/middleware/headers.py,sha256=Jkm61_gH7kCfo3WqTQuIXs1tUK8fWT5vnpjOR8GAcU4,1813
19
+ ccproxy/api/middleware/logging.py,sha256=ab_V4eLrIZxqTUFQKkIOC4MMV55L50ies0kUbt-iFBQ,7064
20
+ ccproxy/api/middleware/request_id.py,sha256=4VLbv1xT8gbRzd5z1kkV-YmeMmlwDf_HoldhrnjK9iA,2350
21
+ ccproxy/api/middleware/server_header.py,sha256=G01U8Wlbj6ov5QEJwjf7UpwcNNPLEqQNom48Gzw-phw,2448
22
+ ccproxy/api/routes/__init__.py,sha256=nuULLSLErxclqDEwNgpIxJVMqjMoBIZNyK7HmFwDxf0,519
23
+ ccproxy/api/routes/claude.py,sha256=DK-X_WePR0RHuybp4z0jWwPJ_HXvfccU16JsGv6cPmM,6659
24
+ ccproxy/api/routes/health.py,sha256=0lGKBT0ll5kn0UgtpA9TQwqVu_q07ITa-5Zgs7xhvvc,17254
25
+ ccproxy/api/routes/metrics.py,sha256=38IwtMYGmcconhB8bIGBmjrvQWB7aZuGK_rQy0d7_wU,40985
26
+ ccproxy/api/routes/proxy.py,sha256=plTNf07Zfg9rvs9W7B2qtFxtmuYekzMpVYrAxg51J3M,8778
27
+ ccproxy/auth/__init__.py,sha256=frT0rDZVGs3zVgaI_z1OuEK6VqAccqRJBBmhlx8tlT4,1967
28
+ ccproxy/auth/bearer.py,sha256=4K9y7kG29NNEhuqqpDvNusmDNWQah90nHGRZxBA1H2o,2015
29
+ ccproxy/auth/credentials_adapter.py,sha256=2BlihJ0KLcIq4hLVHPVaimS0JuZFINzGnBmm_SMPP6c,3279
30
+ ccproxy/auth/dependencies.py,sha256=AOEdZ-6ZfzEMmLevKLlQq_j5YadClSqejiAdxeWQqp4,7083
31
+ ccproxy/auth/exceptions.py,sha256=EUaT7qW0D8HHziX7HglZB-WnALQlbj1lt9OKvyZ97IA,1325
32
+ ccproxy/auth/manager.py,sha256=AHVK4iq3LvavZeSyAmON1ExVA3GLdphe2jO4VuMmImA,2425
33
+ ccproxy/auth/models.py,sha256=z2mkTc-Av2trF3fnqoBEQ7IAfgq96pRGtbjdzrXSFUI,3709
34
+ ccproxy/auth/oauth/__init__.py,sha256=PEymCot9_oIj2mKFIyfmIlznoKwoBuSKqfRvH9k_QR0,508
35
+ ccproxy/auth/oauth/models.py,sha256=c1-egh1iIeOMEkyHNmvGJo0XlGcUJvuLF2gZARqjdfY,2035
36
+ ccproxy/auth/oauth/routes.py,sha256=qTazFFsHGLbE6iIFUBE8CpSl7eRQtAx81V-ew3TaUU4,13643
37
+ ccproxy/auth/oauth/storage.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ ccproxy/auth/storage/__init__.py,sha256=5thfvXhc8HGJblQWMiG6ZPl594RpbTGdAgyAlpAe1TI,324
39
+ ccproxy/auth/storage/base.py,sha256=Gw20tabOKTwey6YrOm09Bxu-MYJuMIrQULLJT6RQ07E,1357
40
+ ccproxy/auth/storage/json_file.py,sha256=3vOHHIuUrjEQVVMOWJS8GX3EHIvQYeaUuTnlhV7mHRU,5055
41
+ ccproxy/auth/storage/keyring.py,sha256=GS9UZrLEXSZPwGxsik6MiDWRpZoStGs-8o30qE8T4kA,6022
42
+ ccproxy/claude_sdk/__init__.py,sha256=HezOzX8RaTw5MTYXZu27aaQYLX9x7jrpjZuT1kwJoKc,404
43
+ ccproxy/claude_sdk/client.py,sha256=L48FJdY3gDSrYXK88tbV5kH6z6HZsjLS_erqdN3OUFA,5500
44
+ ccproxy/claude_sdk/converter.py,sha256=FaMnmvwKEuxmi-atr6zZUWLk8ILqlg2_OVPSlBPBxec,11326
45
+ ccproxy/claude_sdk/options.py,sha256=IZX4yqGjfNlUUOH3XzUOVMBtgXEvk9r1MFzeeiVZihA,3516
46
+ ccproxy/cli/__init__.py,sha256=_vISKEqzAsAYrTZ-j9ScxTUtFZp3RVRbIvGpCcu--SU,262
47
+ ccproxy/cli/helpers.py,sha256=cEgEeXP5Qpqq7Jxqy7wagAkJH_w9jPhQACt00IzhDdQ,3874
48
+ ccproxy/cli/main.py,sha256=Uf5Omn9xGVc8OC4t_9rS9lTPHF_DzS9Q7WGL40ux2yY,5613
49
+ ccproxy/cli/commands/__init__.py,sha256=k6CqHFSM-gbn-tdfjRgTkvwDhCPipZ8-4lbiRn54LPo,185
50
+ ccproxy/cli/commands/auth.py,sha256=k1cejJqKitoHHM7NI2Phc-RzSS7XVJbkXeU53GSHxVA,20474
51
+ ccproxy/cli/commands/serve.py,sha256=LTQeKGft_uL2ZqpG910c_rhHao-DZDCaVbeZfaQIs9I,21988
52
+ ccproxy/cli/commands/config/__init__.py,sha256=uX8VewtrMu5X8VAx5ym1slwNtlICDBEs3S1HfoFe5Qo,376
53
+ ccproxy/cli/commands/config/commands.py,sha256=rXuNO8PIbdiRfU_RkOi_9idamsJ6RzRPagheXjhg5O4,28897
54
+ ccproxy/cli/commands/config/schema_commands.py,sha256=ZSuK_Q1bWp8WUOSbkuwKz_4TwrxkRsA6MOt_-y1eiVs,4218
55
+ ccproxy/cli/docker/__init__.py,sha256=4x9QO8SF9aloJFs4xrUEhGcSmoosIYmjcMTk4-GnHBE,791
56
+ ccproxy/cli/docker/adapter_factory.py,sha256=dbO_MGrrAsyF0DDZMKbrk4oMx2xAMZPihOAviF7D5zY,5022
57
+ ccproxy/cli/docker/params.py,sha256=2Wjn2WW7Q839QItcLQWQRgFoR8lue3GfikVstBZjTmI,7438
58
+ ccproxy/cli/options/__init__.py,sha256=OiToWXDNOXnO-fuPz2H5qXYwp9ht7uB12-4wxqner1Q,340
59
+ ccproxy/cli/options/claude_options.py,sha256=vR7VZ3TVvkAOXC6sbpmkkySiaDLbtUB7TJIZBgwSn-U,6028
60
+ ccproxy/cli/options/core_options.py,sha256=g0B8Up-cPzjUM810uSXxXgtZBLgUXXjI1WodS0pKdJE,862
61
+ ccproxy/cli/options/security_options.py,sha256=571v9ujDk6LS6IicGVDvMhNweCS7HiE_y03TiHgd9PY,1093
62
+ ccproxy/cli/options/server_options.py,sha256=8mtcNDOAOVbKrCyMWySCrkQEMukGc5fsMbIkTi8ztrg,2898
63
+ ccproxy/config/__init__.py,sha256=RVXg7sdADdX9-Z-67OYoNlQxby6UN9tThNtg_XzjZ1I,976
64
+ ccproxy/config/auth.py,sha256=5ztj17H1465i_pI8wOdvffjZ3BHgB5ELEa3i6V0yNyQ,4663
65
+ ccproxy/config/claude.py,sha256=ToNj7a9F_XX6PRPnXI-31VjsZxFvtljGZG-J3jxBRfY,4249
66
+ ccproxy/config/cors.py,sha256=eNxrNuPN7Ujh9_2fIWu6kmOIeYNuqn0NTuCsBYvctFM,2505
67
+ ccproxy/config/discovery.py,sha256=6-exBQ5FU0z0H7ViNVYSSFPg8qKXtqQU6VDB8aTLJj8,2283
68
+ ccproxy/config/docker_settings.py,sha256=w1lCxKyAwe0Vrof4zNrnXaFzA8zjoID0tnFR4zW9bM4,8409
69
+ ccproxy/config/loader.py,sha256=gX9R6GY9zE733VUIV-4ofPivgR4aftYxgqat5RXPPz4,3152
70
+ ccproxy/config/observability.py,sha256=5AwQFEFxJLUStGP5gjL_5i8Hk8KdrXKY7ocITYg8jZQ,5466
71
+ ccproxy/config/pricing.py,sha256=YnqaP8wvdA2G94S9EkA9ibSBLZoabeV7HqlCv8Yez1g,2607
72
+ ccproxy/config/reverse_proxy.py,sha256=hep4ubV7-4ZgdO1_WqY56b5yrYyHCdQgYayUHKH3tfo,866
73
+ ccproxy/config/scheduler.py,sha256=pF0lNMhzsumEgm24X16xNsRyymE6aMoAREaMrZUO_Y8,2492
74
+ ccproxy/config/security.py,sha256=8e8o2OsYgaOKpZoBcA4qqIcYp3NHYIR0QfM1HNUxvxg,330
75
+ ccproxy/config/server.py,sha256=L42FUX8uEdVYP-3VP74lfTGVkzQgsv6PEOopEfHDb0U,2317
76
+ ccproxy/config/settings.py,sha256=5lNv0IovU6yE7SuHe_o_z43iwwvn_eA0icm7uNGR-rQ,17687
77
+ ccproxy/config/validators.py,sha256=A1AIwQnpuC-FSDaZDYisgx5RgHRS9pHs1feuKQKQKrk,5771
78
+ ccproxy/core/__init__.py,sha256=ZreOdlUlCn7R5GlS3EdpvIj5fO9sySbQKnuR8o6vwXI,6400
79
+ ccproxy/core/async_utils.py,sha256=MQlyuPdeZrvcMN4ZGKnOP7FuQemsAv638TXMo7fRWgk,19943
80
+ ccproxy/core/constants.py,sha256=mmkcOfKNnvu3h5paZ6GL72ecAK4XdBI4tXlmgImGyzI,2735
81
+ ccproxy/core/errors.py,sha256=05tTMlBwNS1AWihS0LFOCsevCjZcOPgSUoH8sGuecy0,7091
82
+ ccproxy/core/http.py,sha256=pj3UM9TDL13_sU0ULGuA2wvVg58uxOCy-ib3zugjelo,9317
83
+ ccproxy/core/http_transformers.py,sha256=kBu4H9tAeiyMAfSlybZnRuCAe_iL4dVxwI8HDEL5L3o,15663
84
+ ccproxy/core/interfaces.py,sha256=WliYsGhdF3-CxfH8XSng0JVaWOhLgJZDAwiZT1gYLLg,6405
85
+ ccproxy/core/logging.py,sha256=UuIjAHU21Sv1yljCfdYEdGiZP934ovOD3xvzlOG0MOI,6841
86
+ ccproxy/core/middleware.py,sha256=ot6rr84h9lLlHhBol7rHBNpB-RWlCEumUcNlv-4thmk,3370
87
+ ccproxy/core/proxy.py,sha256=lsJzNuUnxHN75h6d9TRRcYt2mQ03tZRHwH2a7vQpvIA,4625
88
+ ccproxy/core/system.py,sha256=91rMtlRtY4yxSsnPV5qZJaXHNFzvcrRZ1nQwS4CwG68,1019
89
+ ccproxy/core/transformers.py,sha256=uPT9PsV_moU4dMg6qlOyvnAomrFmT-rKBwmWtEFkkMA,7709
90
+ ccproxy/core/types.py,sha256=qoR8IHcAHnUekml2lotEnxkPqwI-5qhQdJUWgtw7S-Q,3631
91
+ ccproxy/core/validators.py,sha256=k2z71lz-zKhRdtx3tYgJllqFnEcQ-eivj1kf3aVej0s,7367
92
+ ccproxy/docker/__init__.py,sha256=gO9FJepIWneXPANgsAJKx_VL9rt7pcX3hbRcwnSyzJk,1833
93
+ ccproxy/docker/adapter.py,sha256=P-GeLVu5hl4oMHQrQJU3AHnUxrps-iUd06r7IVnncoY,21176
94
+ ccproxy/docker/docker_path.py,sha256=U_Di1bJDxDZNHW0cxGL31PD6RGKS9Sngs6_D7burmd0,6419
95
+ ccproxy/docker/middleware.py,sha256=hFSvTf77zrOWTH_T3lAk_O7jNSnYDPV_YQoQ82deBsM,3282
96
+ ccproxy/docker/models.py,sha256=RdgHpb4Oyzh19RfWZn-R61OX2R7c1cwAh5ZT7-egJuQ,7500
97
+ ccproxy/docker/protocol.py,sha256=4asLeKpP1Su9HzEQVm_AIdMa1nzWMuGpuJ2BHPEkEVA,6561
98
+ ccproxy/docker/stream_process.py,sha256=XahXegGG6B-7RsZAb9J8OA7sz7xi8CmRzEI4vBJLwTI,9071
99
+ ccproxy/docker/validators.py,sha256=OY-dkU88CLIeMBfv4P5QoVB7miQ8BD7kIgaMHxF7TsU,5764
100
+ ccproxy/models/__init__.py,sha256=XOkiHVzKuMSL8v0NNdL4WxBKrgeItnCeu9p9yYphQkY,2568
101
+ ccproxy/models/errors.py,sha256=B2rbL-17ZI9emDOca2A2fLOcH1-_77t_xqDmiPf6fgY,1329
102
+ ccproxy/models/messages.py,sha256=vSr2O57-h74fa9Y75HGgZZ9WEpJcoJpO9ZQ9Z5438xw,7740
103
+ ccproxy/models/requests.py,sha256=bhpxN19rvV5gyZPQxriFotJ9rwtMaZF9astMT4qnMz0,2766
104
+ ccproxy/models/responses.py,sha256=PPZ1ajF8Z9anPbY04krxPnFmj-9lAEEsPgSbaoJePOc,7044
105
+ ccproxy/models/types.py,sha256=27MUg7pVGrK5oWzcrtG_IAoScEcrF20thO94bxZXQww,2729
106
+ ccproxy/observability/__init__.py,sha256=5n9yQBbMLAQVAEJZD-2t4CKfI0Wx2aoDH7d2ynPM4bg,1503
107
+ ccproxy/observability/access_logger.py,sha256=hhP-dgWPmbFhviPqJhrb7AlRcvfF1uONbKlqzDvAvAI,13120
108
+ ccproxy/observability/context.py,sha256=VOFLP_OUDvPeIX45l-hbnbRXBLM1Hr9g0Prb2FSTIA0,13969
109
+ ccproxy/observability/metrics.py,sha256=iVEkAWMXsnWjlAacFcMhl_cVBb5OQTt0f_UzQZ7jpCA,16197
110
+ ccproxy/observability/pushgateway.py,sha256=TlaVhqhzvL4kTeNsnoTfPYMvs1yD5-v0xkhrB2nTVsw,12737
111
+ ccproxy/observability/sse_events.py,sha256=CVumUxVlXV5tpqVozymaLW4pBTEM90KBYWS9jAJ6Dzk,10697
112
+ ccproxy/observability/stats_printer.py,sha256=_-VU86UbnJHJMWJgEulKcazJRlF_s3LFw2jocPNrknE,30207
113
+ ccproxy/observability/storage/__init__.py,sha256=iAnhODA2PD3PxheoRgJv5KFCTUK_QwxxFa2Ne230RTg,47
114
+ ccproxy/observability/storage/duckdb_simple.py,sha256=kQXJhcc8tWtnGXIZkHtia_4CU1oLlehdCl7sBvzQT-w,24799
115
+ ccproxy/observability/storage/models.py,sha256=njN-sY0kQZICo0um25JwQ8JBplDsPZwitG1wSwO-QJM,1451
116
+ ccproxy/pricing/__init__.py,sha256=8EKUTusWT_URBO8_jzHQ2JWIv11JYZxiyk1WoA_slHE,450
117
+ ccproxy/pricing/cache.py,sha256=UBr4iTS3220VAunVmyOp_pJpKJRn6eij2GZO42XJHps,6531
118
+ ccproxy/pricing/loader.py,sha256=b7rdYjMkWp4ImQBQBqq6h0Pp7yAHZWgh5IlzKpnriuU,9486
119
+ ccproxy/pricing/models.py,sha256=um5ggAtT1QxdSPowHcBWk_ZlnaN6yus2Gz5X2a-BW74,3582
120
+ ccproxy/pricing/updater.py,sha256=RFhofp5m4CUc4huEyg5d4a3JRXg52kDsRnm7-IxvQXs,10200
121
+ ccproxy/scheduler/__init__.py,sha256=6eEXMl8B3l69-O4OCwIYeL_DPGV59TJ9n2Q330hyh5g,1139
122
+ ccproxy/scheduler/core.py,sha256=TCBn8DLzPjd6juLUYF3zKmBzw4HVm37zNy01kpOshzM,10343
123
+ ccproxy/scheduler/exceptions.py,sha256=k7dcid0_te7IwwQaad-Jkj7MWFBgIOdgD_y_n5joio0,817
124
+ ccproxy/scheduler/manager.py,sha256=WXREf7nCUqINk5rv--l-xFiecH_qddBfXHe4BhYKDVw,5793
125
+ ccproxy/scheduler/registry.py,sha256=E2636J1Pj5G9cqr3UZO4D8hVCzt3cMd8rkanvXLF72M,4062
126
+ ccproxy/scheduler/tasks.py,sha256=y6FdhkXg7Irgf5CAqXAWlR5j2viZQKuRuG3ydtWpKdc,16058
127
+ ccproxy/services/__init__.py,sha256=ZvnelD15eFLlWHsucYXBFGNrdT7ncdP1KLnqzJNGOCs,251
128
+ ccproxy/services/claude_sdk_service.py,sha256=m1T7tJAVfxI-vV2U1zsTPv1AtDHHfh9PE6ld_w_nPos,22086
129
+ ccproxy/services/proxy_service.py,sha256=2IIR-zAy_MyU9G3IzUiGtECOa4nEApMBxAIihaQFWVw,59232
130
+ ccproxy/services/credentials/__init__.py,sha256=fkCWqxlUyGVA1mxGicn4cvdbYJQo09SG9NoGKzUun3s,1394
131
+ ccproxy/services/credentials/config.py,sha256=97W3GqtxZlBv45oDHJ-plsHiSeFvNI-FTMZEw4CsPes,3221
132
+ ccproxy/services/credentials/manager.py,sha256=p7yLb3nO34nh2yVb3HIFrwDEM2RQE4ym-GKW8tkaZj4,19971
133
+ ccproxy/services/credentials/oauth_client.py,sha256=ldnRl1XQsKEVGiOYc2gI8br92CEY9jBCoPeaJCaZS7M,16605
134
+ ccproxy/static/.keep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
+ ccproxy/testing/__init__.py,sha256=-W01VLA1RfopmwVaeWH32gstERNsjFcNIIynZjZjzls,1071
136
+ ccproxy/testing/config.py,sha256=TQLEYHCwdyXzlnk5xQOjIUD0cef5QfWd70nAZG0g_xY,4875
137
+ ccproxy/testing/content_generation.py,sha256=in2F_W1RSnQYKXYCavMrYXPLBrqVn_Bogx84oF242iY,8585
138
+ ccproxy/testing/mock_responses.py,sha256=bskAbk2YecbpAoOCVrP4LCkO-lQhspwhAlpC2pXdypY,9212
139
+ ccproxy/testing/response_handlers.py,sha256=TSfBHzHLw8d3Y95zJ8wYewU9f5Wh89bl7DRfa-49ze0,6311
140
+ ccproxy/testing/scenarios.py,sha256=lVTkf1TbqPnWAfY2VbXez6dSFHsohuXerDmh37iC5VU,9200
141
+ ccproxy/utils/__init__.py,sha256=MBEWNgdZh4Ev-LcltUhkMBboR1QHYNeri3oOy1KQeDo,213
142
+ ccproxy/utils/cost_calculator.py,sha256=mHquyA_1vnPVZ2smjdPwThCJtGu-rF9n8ZvIrAwTF78,7276
143
+ ccproxy/utils/streaming_metrics.py,sha256=JkvmWJ9s1fuKi7x1NoSoderUuT-mU6MQfbnN5GmziYE,7761
144
+ ccproxy_api-0.1.0.dist-info/METADATA,sha256=b1qXrdXFhbT_iONZnb24-_IaDp5qSAfDLqXVBlGC2e0,8602
145
+ ccproxy_api-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
146
+ ccproxy_api-0.1.0.dist-info/entry_points.txt,sha256=_7fyE285w0AZtqkRkP6ZIFICeDO3D69RZTiHOVrRSI8,45
147
+ ccproxy_api-0.1.0.dist-info/licenses/LICENSE,sha256=httxSCpTrEOkipisMeGXSrZhTB-4MRIorQU0hS1B6eQ,1066
148
+ ccproxy_api-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ccproxy = ccproxy.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CaddyGlow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.