ccproxy-api 0.1.1__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.
- ccproxy/_version.py +2 -2
- ccproxy/adapters/openai/__init__.py +1 -2
- ccproxy/adapters/openai/adapter.py +218 -180
- ccproxy/adapters/openai/streaming.py +247 -65
- ccproxy/api/__init__.py +0 -3
- ccproxy/api/app.py +173 -40
- ccproxy/api/dependencies.py +65 -3
- ccproxy/api/middleware/errors.py +3 -7
- ccproxy/api/middleware/headers.py +0 -2
- ccproxy/api/middleware/logging.py +4 -3
- ccproxy/api/middleware/request_content_logging.py +297 -0
- ccproxy/api/middleware/request_id.py +5 -0
- ccproxy/api/middleware/server_header.py +0 -4
- ccproxy/api/routes/__init__.py +9 -1
- ccproxy/api/routes/claude.py +23 -32
- ccproxy/api/routes/health.py +58 -4
- ccproxy/api/routes/mcp.py +171 -0
- ccproxy/api/routes/metrics.py +4 -8
- ccproxy/api/routes/permissions.py +217 -0
- ccproxy/api/routes/proxy.py +0 -53
- ccproxy/api/services/__init__.py +6 -0
- ccproxy/api/services/permission_service.py +368 -0
- ccproxy/api/ui/__init__.py +6 -0
- ccproxy/api/ui/permission_handler_protocol.py +33 -0
- ccproxy/api/ui/terminal_permission_handler.py +593 -0
- ccproxy/auth/conditional.py +2 -2
- ccproxy/auth/dependencies.py +1 -1
- ccproxy/auth/oauth/models.py +0 -1
- ccproxy/auth/oauth/routes.py +1 -3
- ccproxy/auth/storage/json_file.py +0 -1
- ccproxy/auth/storage/keyring.py +0 -3
- ccproxy/claude_sdk/__init__.py +2 -0
- ccproxy/claude_sdk/client.py +91 -8
- ccproxy/claude_sdk/converter.py +405 -210
- ccproxy/claude_sdk/options.py +88 -19
- ccproxy/claude_sdk/parser.py +200 -0
- ccproxy/claude_sdk/streaming.py +286 -0
- ccproxy/cli/commands/__init__.py +5 -1
- ccproxy/cli/commands/auth.py +2 -4
- ccproxy/cli/commands/permission_handler.py +553 -0
- ccproxy/cli/commands/serve.py +52 -12
- ccproxy/cli/docker/params.py +0 -4
- ccproxy/cli/helpers.py +0 -2
- ccproxy/cli/main.py +6 -17
- ccproxy/cli/options/claude_options.py +41 -1
- ccproxy/cli/options/core_options.py +0 -3
- ccproxy/cli/options/security_options.py +0 -2
- ccproxy/cli/options/server_options.py +3 -2
- ccproxy/config/auth.py +0 -1
- ccproxy/config/claude.py +78 -2
- ccproxy/config/discovery.py +0 -1
- ccproxy/config/docker_settings.py +0 -1
- ccproxy/config/loader.py +1 -4
- ccproxy/config/scheduler.py +20 -0
- ccproxy/config/security.py +7 -2
- ccproxy/config/server.py +5 -0
- ccproxy/config/settings.py +15 -7
- ccproxy/config/validators.py +1 -1
- ccproxy/core/async_utils.py +1 -4
- ccproxy/core/errors.py +45 -1
- ccproxy/core/http_transformers.py +4 -3
- ccproxy/core/interfaces.py +2 -2
- ccproxy/core/logging.py +97 -95
- ccproxy/core/middleware.py +1 -1
- ccproxy/core/proxy.py +1 -1
- ccproxy/core/transformers.py +1 -1
- ccproxy/core/types.py +1 -1
- ccproxy/docker/models.py +1 -1
- ccproxy/docker/protocol.py +0 -3
- ccproxy/models/__init__.py +41 -0
- ccproxy/models/claude_sdk.py +420 -0
- ccproxy/models/messages.py +45 -18
- ccproxy/models/permissions.py +115 -0
- ccproxy/models/requests.py +1 -1
- ccproxy/models/responses.py +64 -1
- ccproxy/observability/access_logger.py +1 -2
- ccproxy/observability/context.py +17 -1
- ccproxy/observability/metrics.py +1 -3
- ccproxy/observability/pushgateway.py +0 -2
- ccproxy/observability/stats_printer.py +2 -4
- ccproxy/observability/storage/duckdb_simple.py +1 -1
- ccproxy/observability/storage/models.py +0 -1
- ccproxy/pricing/cache.py +0 -1
- ccproxy/pricing/loader.py +5 -21
- ccproxy/pricing/updater.py +0 -1
- ccproxy/scheduler/__init__.py +1 -0
- ccproxy/scheduler/core.py +6 -6
- ccproxy/scheduler/manager.py +35 -7
- ccproxy/scheduler/registry.py +1 -1
- ccproxy/scheduler/tasks.py +127 -2
- ccproxy/services/claude_sdk_service.py +225 -329
- ccproxy/services/credentials/manager.py +0 -1
- ccproxy/services/credentials/oauth_client.py +1 -2
- ccproxy/services/proxy_service.py +93 -222
- ccproxy/testing/config.py +1 -1
- ccproxy/testing/mock_responses.py +0 -1
- ccproxy/utils/model_mapping.py +197 -0
- ccproxy/utils/models_provider.py +150 -0
- ccproxy/utils/simple_request_logger.py +284 -0
- ccproxy/utils/version_checker.py +184 -0
- {ccproxy_api-0.1.1.dist-info → ccproxy_api-0.1.3.dist-info}/METADATA +63 -2
- ccproxy_api-0.1.3.dist-info/RECORD +166 -0
- {ccproxy_api-0.1.1.dist-info → ccproxy_api-0.1.3.dist-info}/entry_points.txt +1 -0
- ccproxy_api-0.1.1.dist-info/RECORD +0 -149
- /ccproxy/scheduler/{exceptions.py → errors.py} +0 -0
- {ccproxy_api-0.1.1.dist-info → ccproxy_api-0.1.3.dist-info}/WHEEL +0 -0
- {ccproxy_api-0.1.1.dist-info → ccproxy_api-0.1.3.dist-info}/licenses/LICENSE +0 -0
ccproxy/core/errors.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Core error types for the proxy system."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from fastapi import HTTPException
|
|
6
6
|
|
|
@@ -234,6 +234,45 @@ class DockerError(ClaudeProxyError):
|
|
|
234
234
|
)
|
|
235
235
|
|
|
236
236
|
|
|
237
|
+
class PermissionRequestError(ClaudeProxyError):
|
|
238
|
+
"""Base exception for permission request-related errors."""
|
|
239
|
+
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class PermissionNotFoundError(PermissionRequestError):
|
|
244
|
+
"""Raised when permission request is not found."""
|
|
245
|
+
|
|
246
|
+
def __init__(self, confirmation_id: str) -> None:
|
|
247
|
+
super().__init__(
|
|
248
|
+
message=f"Permission request '{confirmation_id}' not found",
|
|
249
|
+
error_type="not_found_error",
|
|
250
|
+
status_code=404,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class PermissionExpiredError(PermissionRequestError):
|
|
255
|
+
"""Raised when permission request has expired."""
|
|
256
|
+
|
|
257
|
+
def __init__(self, confirmation_id: str) -> None:
|
|
258
|
+
super().__init__(
|
|
259
|
+
message=f"Permission request '{confirmation_id}' has expired",
|
|
260
|
+
error_type="expired_error",
|
|
261
|
+
status_code=410,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class PermissionAlreadyResolvedError(PermissionRequestError):
|
|
266
|
+
"""Raised when trying to resolve an already resolved request."""
|
|
267
|
+
|
|
268
|
+
def __init__(self, confirmation_id: str, status: str) -> None:
|
|
269
|
+
super().__init__(
|
|
270
|
+
message=f"Permission request '{confirmation_id}' already resolved with status: {status}",
|
|
271
|
+
error_type="conflict_error",
|
|
272
|
+
status_code=409,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
237
276
|
__all__ = [
|
|
238
277
|
# Core proxy errors
|
|
239
278
|
"ProxyError",
|
|
@@ -253,4 +292,9 @@ __all__ = [
|
|
|
253
292
|
"TimeoutError",
|
|
254
293
|
"ServiceUnavailableError",
|
|
255
294
|
"DockerError",
|
|
295
|
+
# Permission errors
|
|
296
|
+
"PermissionRequestError",
|
|
297
|
+
"PermissionNotFoundError",
|
|
298
|
+
"PermissionExpiredError",
|
|
299
|
+
"PermissionAlreadyResolvedError",
|
|
256
300
|
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""HTTP-level transformers for proxy service."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import TYPE_CHECKING, Any
|
|
5
4
|
|
|
6
5
|
import structlog
|
|
@@ -18,6 +17,8 @@ logger = structlog.get_logger(__name__)
|
|
|
18
17
|
# Claude Code system prompt constants
|
|
19
18
|
claude_code_prompt = "You are Claude Code, Anthropic's official CLI for Claude."
|
|
20
19
|
|
|
20
|
+
# claude_code_prompt = "<system-reminder>\nAs you answer the user's questions, you can use the following context:\n# important-instruction-reminders\nDo what has been asked; nothing more, nothing less.\nNEVER create files unless they're absolutely necessary for achieving your goal.\nALWAYS prefer editing an existing file to creating a new one.\nNEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.\n\n \n IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.\n</system-reminder>\n"
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
def get_claude_code_prompt() -> dict[str, Any]:
|
|
23
24
|
"""Get the Claude Code system prompt with cache control."""
|
|
@@ -179,7 +180,7 @@ class HTTPRequestTransformer(RequestTransformer):
|
|
|
179
180
|
|
|
180
181
|
# Claude CLI identity headers
|
|
181
182
|
proxy_headers["x-app"] = "cli"
|
|
182
|
-
proxy_headers["User-Agent"] = "claude-cli/1.0.
|
|
183
|
+
proxy_headers["User-Agent"] = "claude-cli/1.0.60 (external, cli)"
|
|
183
184
|
|
|
184
185
|
# Stainless SDK compatibility headers
|
|
185
186
|
proxy_headers["X-Stainless-Lang"] = "js"
|
|
@@ -189,7 +190,7 @@ class HTTPRequestTransformer(RequestTransformer):
|
|
|
189
190
|
proxy_headers["X-Stainless-OS"] = "Linux"
|
|
190
191
|
proxy_headers["X-Stainless-Arch"] = "x64"
|
|
191
192
|
proxy_headers["X-Stainless-Runtime"] = "node"
|
|
192
|
-
proxy_headers["X-Stainless-Runtime-Version"] = "
|
|
193
|
+
proxy_headers["X-Stainless-Runtime-Version"] = "v24.3.0"
|
|
193
194
|
|
|
194
195
|
# Standard HTTP headers for proper API interaction
|
|
195
196
|
proxy_headers["accept-language"] = "*"
|
ccproxy/core/interfaces.py
CHANGED
|
@@ -6,10 +6,10 @@ providing a single location for defining contracts and protocols.
|
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from collections.abc import AsyncIterator
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Protocol, TypeVar, runtime_checkable
|
|
10
10
|
|
|
11
11
|
from ccproxy.auth.models import ClaudeCredentials
|
|
12
|
-
from ccproxy.core.types import
|
|
12
|
+
from ccproxy.core.types import TransformContext
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
ccproxy/core/logging.py
CHANGED
|
@@ -1,45 +1,25 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import sys
|
|
3
|
-
from
|
|
4
|
-
from typing import Any
|
|
3
|
+
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import structlog
|
|
7
6
|
from structlog.stdlib import BoundLogger
|
|
8
7
|
from structlog.typing import Processor
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
def configure_structlog(
|
|
12
|
-
"""Configure structlog with
|
|
13
|
-
#
|
|
14
|
-
# Dev mode (DEBUG): only hours without microseconds
|
|
15
|
-
# Info mode: full date without microseconds
|
|
16
|
-
if log_level.upper() == "DEBUG":
|
|
17
|
-
timestamper = structlog.processors.TimeStamper(fmt="%H:%M:%S")
|
|
18
|
-
else:
|
|
19
|
-
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
|
|
20
|
-
|
|
21
|
-
# Processors that will be used for structlog loggers
|
|
10
|
+
def configure_structlog(log_level: int = logging.INFO) -> None:
|
|
11
|
+
"""Configure structlog with shared processors following canonical pattern."""
|
|
12
|
+
# Shared processors for all structlog loggers
|
|
22
13
|
processors: list[Processor] = [
|
|
14
|
+
structlog.contextvars.merge_contextvars, # For request context in web apps
|
|
23
15
|
structlog.stdlib.filter_by_level,
|
|
16
|
+
structlog.stdlib.add_log_level,
|
|
17
|
+
structlog.stdlib.add_logger_name,
|
|
24
18
|
]
|
|
25
19
|
|
|
26
|
-
#
|
|
27
|
-
if log_level
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
processors.extend(
|
|
31
|
-
[
|
|
32
|
-
structlog.stdlib.add_log_level,
|
|
33
|
-
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
34
|
-
timestamper,
|
|
35
|
-
structlog.processors.StackInfoRenderer(),
|
|
36
|
-
structlog.processors.format_exc_info,
|
|
37
|
-
structlog.processors.UnicodeDecoder(),
|
|
38
|
-
]
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# Only add CallsiteParameterAdder if NOT in INFO mode
|
|
42
|
-
if log_level.upper() != "INFO":
|
|
20
|
+
# Add debug-specific processors
|
|
21
|
+
if log_level < logging.INFO:
|
|
22
|
+
# Dev mode (DEBUG): add callsite information
|
|
43
23
|
processors.append(
|
|
44
24
|
structlog.processors.CallsiteParameterAdder(
|
|
45
25
|
parameters=[
|
|
@@ -49,92 +29,115 @@ def configure_structlog(json_logs: bool = False, log_level: str = "INFO") -> Non
|
|
|
49
29
|
)
|
|
50
30
|
)
|
|
51
31
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
32
|
+
# Common processors for all log levels
|
|
33
|
+
processors.extend(
|
|
34
|
+
[
|
|
35
|
+
# Use human-readable timestamp for structlog logs in debug mode, normal otherwise
|
|
36
|
+
structlog.processors.TimeStamper(
|
|
37
|
+
fmt="%H:%M:%S" if log_level < logging.INFO else "%Y-%m-%d %H:%M:%S"
|
|
38
|
+
),
|
|
39
|
+
structlog.processors.StackInfoRenderer(),
|
|
40
|
+
structlog.dev.set_exc_info, # Handle exceptions properly
|
|
41
|
+
# This MUST be the last processor - allows different renderers per handler
|
|
42
|
+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|
43
|
+
]
|
|
44
|
+
)
|
|
55
45
|
|
|
56
46
|
structlog.configure(
|
|
57
47
|
processors=processors,
|
|
58
48
|
context_class=dict,
|
|
59
49
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
60
50
|
wrapper_class=structlog.stdlib.BoundLogger,
|
|
61
|
-
cache_logger_on_first_use=
|
|
51
|
+
cache_logger_on_first_use=True, # Cache for performance
|
|
62
52
|
)
|
|
63
53
|
|
|
64
54
|
|
|
65
55
|
def setup_logging(
|
|
66
|
-
json_logs: bool = False,
|
|
56
|
+
json_logs: bool = False, log_level_name: str = "DEBUG", log_file: str | None = None
|
|
67
57
|
) -> BoundLogger:
|
|
68
58
|
"""
|
|
69
|
-
Setup logging for the entire application
|
|
59
|
+
Setup logging for the entire application using canonical structlog pattern.
|
|
70
60
|
Returns a structlog logger instance.
|
|
71
61
|
"""
|
|
72
|
-
|
|
62
|
+
log_level = getattr(logging, log_level_name.upper(), logging.INFO)
|
|
63
|
+
|
|
64
|
+
# Get root logger and set level BEFORE configuring structlog
|
|
73
65
|
root_logger = logging.getLogger()
|
|
74
|
-
root_logger.setLevel(
|
|
66
|
+
root_logger.setLevel(log_level)
|
|
75
67
|
|
|
76
|
-
# Configure structlog
|
|
77
|
-
configure_structlog(
|
|
68
|
+
# 1. Configure structlog with shared processors
|
|
69
|
+
configure_structlog(log_level=log_level)
|
|
78
70
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
handler.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|
|
71
|
+
# 2. Setup root logger handlers
|
|
72
|
+
root_logger.handlers = [] # Clear any existing handlers
|
|
82
73
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
structlog.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
# 3. Create shared processors for foreign (stdlib) logs
|
|
75
|
+
shared_processors = [
|
|
76
|
+
structlog.contextvars.merge_contextvars,
|
|
77
|
+
structlog.stdlib.add_log_level,
|
|
78
|
+
structlog.stdlib.add_logger_name,
|
|
79
|
+
structlog.dev.set_exc_info,
|
|
80
|
+
]
|
|
89
81
|
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
# Add debug processors if needed
|
|
83
|
+
if log_level < logging.INFO:
|
|
84
|
+
shared_processors.append(
|
|
85
|
+
structlog.processors.CallsiteParameterAdder( # type: ignore[arg-type]
|
|
86
|
+
parameters=[
|
|
87
|
+
structlog.processors.CallsiteParameter.FILENAME,
|
|
88
|
+
structlog.processors.CallsiteParameter.LINENO,
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
)
|
|
96
92
|
|
|
97
|
-
#
|
|
98
|
-
|
|
93
|
+
# Add appropriate timestamper for console vs file
|
|
94
|
+
console_timestamper = (
|
|
95
|
+
structlog.processors.TimeStamper(fmt="%H:%M:%S")
|
|
96
|
+
if log_level < logging.INFO
|
|
97
|
+
else structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
|
|
98
|
+
)
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
if log_level.upper() != "INFO":
|
|
102
|
-
foreign_pre_chain.append(structlog.stdlib.add_logger_name)
|
|
100
|
+
file_timestamper = structlog.processors.TimeStamper(fmt="iso")
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
# 4. Setup console handler with ConsoleRenderer
|
|
103
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
104
|
+
console_handler.setLevel(log_level)
|
|
105
|
+
console_renderer = (
|
|
106
|
+
structlog.processors.JSONRenderer()
|
|
107
|
+
if json_logs
|
|
108
|
+
else structlog.dev.ConsoleRenderer()
|
|
109
|
+
)
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
# Console gets human-readable timestamps for both structlog and stdlib logs
|
|
112
|
+
console_processors = shared_processors + [console_timestamper]
|
|
113
|
+
console_handler.setFormatter(
|
|
107
114
|
structlog.stdlib.ProcessorFormatter(
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
foreign_pre_chain=console_processors,
|
|
116
|
+
processor=console_renderer,
|
|
110
117
|
)
|
|
111
118
|
)
|
|
119
|
+
root_logger.addHandler(console_handler)
|
|
112
120
|
|
|
113
|
-
#
|
|
114
|
-
handlers: list[logging.Handler] = [handler]
|
|
115
|
-
|
|
116
|
-
# Add file handler if log_file is specified
|
|
121
|
+
# 5. Setup file handler with JSONRenderer (if log_file provided)
|
|
117
122
|
if log_file:
|
|
118
|
-
from pathlib import Path
|
|
119
|
-
|
|
120
123
|
# Ensure parent directory exists
|
|
121
124
|
log_path = Path(log_file)
|
|
122
125
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
file_handler
|
|
126
|
-
|
|
127
|
+
file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True)
|
|
128
|
+
file_handler.setLevel(log_level)
|
|
129
|
+
|
|
130
|
+
# File gets ISO timestamps for both structlog and stdlib logs
|
|
131
|
+
file_processors = shared_processors + [file_timestamper]
|
|
127
132
|
file_handler.setFormatter(
|
|
128
133
|
structlog.stdlib.ProcessorFormatter(
|
|
134
|
+
foreign_pre_chain=file_processors,
|
|
129
135
|
processor=structlog.processors.JSONRenderer(),
|
|
130
|
-
foreign_pre_chain=foreign_pre_chain,
|
|
131
136
|
)
|
|
132
137
|
)
|
|
133
|
-
|
|
138
|
+
root_logger.addHandler(file_handler)
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# Make sure uvicorn and fastapi loggers use our configuration
|
|
140
|
+
# 6. Configure stdlib loggers to propagate to our handlers
|
|
138
141
|
for logger_name in [
|
|
139
142
|
"uvicorn",
|
|
140
143
|
"uvicorn.access",
|
|
@@ -146,27 +149,23 @@ def setup_logging(
|
|
|
146
149
|
logger.handlers = [] # Remove default handlers
|
|
147
150
|
logger.propagate = True # Use root logger's handlers
|
|
148
151
|
|
|
149
|
-
#
|
|
150
|
-
|
|
152
|
+
# In DEBUG mode, let all logs through at DEBUG level
|
|
153
|
+
# Otherwise, reduce uvicorn noise by setting to WARNING
|
|
154
|
+
if log_level == logging.DEBUG:
|
|
155
|
+
logger.setLevel(logging.DEBUG)
|
|
156
|
+
elif logger_name.startswith("uvicorn"):
|
|
151
157
|
logger.setLevel(logging.WARNING)
|
|
152
158
|
else:
|
|
153
|
-
logger.setLevel(
|
|
159
|
+
logger.setLevel(log_level)
|
|
154
160
|
|
|
155
161
|
# Configure httpx logger separately - INFO when app is DEBUG, WARNING otherwise
|
|
156
162
|
httpx_logger = logging.getLogger("httpx")
|
|
157
|
-
httpx_logger.handlers = []
|
|
158
|
-
httpx_logger.propagate = True
|
|
159
|
-
if log_level.
|
|
160
|
-
httpx_logger.setLevel(logging.INFO)
|
|
161
|
-
else:
|
|
162
|
-
httpx_logger.setLevel(logging.WARNING)
|
|
163
|
-
|
|
164
|
-
# Set noisy HTTP-related loggers to WARNING when app log level >= WARNING, else use app log level
|
|
165
|
-
app_log_level = getattr(logging, log_level.upper(), logging.INFO)
|
|
166
|
-
noisy_log_level = (
|
|
167
|
-
logging.WARNING if app_log_level <= logging.WARNING else app_log_level
|
|
168
|
-
)
|
|
163
|
+
httpx_logger.handlers = []
|
|
164
|
+
httpx_logger.propagate = True
|
|
165
|
+
httpx_logger.setLevel(logging.INFO if log_level < logging.INFO else logging.WARNING)
|
|
169
166
|
|
|
167
|
+
# Set noisy HTTP-related loggers to WARNING
|
|
168
|
+
noisy_log_level = logging.WARNING if log_level <= logging.WARNING else log_level
|
|
170
169
|
for noisy_logger_name in [
|
|
171
170
|
"urllib3",
|
|
172
171
|
"urllib3.connectionpool",
|
|
@@ -174,10 +173,13 @@ def setup_logging(
|
|
|
174
173
|
"aiohttp",
|
|
175
174
|
"httpcore",
|
|
176
175
|
"httpcore.http11",
|
|
176
|
+
"fastapi_mcp",
|
|
177
|
+
"sse_starlette",
|
|
178
|
+
"mcp",
|
|
177
179
|
]:
|
|
178
180
|
noisy_logger = logging.getLogger(noisy_logger_name)
|
|
179
|
-
noisy_logger.handlers = []
|
|
180
|
-
noisy_logger.propagate = True
|
|
181
|
+
noisy_logger.handlers = []
|
|
182
|
+
noisy_logger.propagate = True
|
|
181
183
|
noisy_logger.setLevel(noisy_log_level)
|
|
182
184
|
|
|
183
185
|
return structlog.get_logger() # type: ignore[no-any-return]
|
ccproxy/core/middleware.py
CHANGED
ccproxy/core/proxy.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Core proxy abstractions for handling HTTP and WebSocket connections."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
5
5
|
|
|
6
6
|
from ccproxy.core.types import ProxyRequest, ProxyResponse
|
|
7
7
|
|
ccproxy/core/transformers.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Core transformer abstractions for request/response transformation."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, runtime_checkable
|
|
5
5
|
|
|
6
6
|
from structlog import get_logger
|
|
7
7
|
|
ccproxy/core/types.py
CHANGED
ccproxy/docker/models.py
CHANGED
ccproxy/docker/protocol.py
CHANGED
ccproxy/models/__init__.py
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
"""Pydantic models for Claude Proxy API Server."""
|
|
2
2
|
|
|
3
|
+
from .claude_sdk import (
|
|
4
|
+
AssistantMessage,
|
|
5
|
+
ContentBlock,
|
|
6
|
+
ExtendedContentBlock,
|
|
7
|
+
ResultMessage,
|
|
8
|
+
ResultMessageBlock,
|
|
9
|
+
SDKContentBlock,
|
|
10
|
+
SDKMessageMode,
|
|
11
|
+
TextBlock,
|
|
12
|
+
ToolResultBlock,
|
|
13
|
+
ToolResultSDKBlock,
|
|
14
|
+
ToolUseBlock,
|
|
15
|
+
ToolUseSDKBlock,
|
|
16
|
+
UserMessage,
|
|
17
|
+
convert_sdk_result_message,
|
|
18
|
+
convert_sdk_system_message,
|
|
19
|
+
convert_sdk_text_block,
|
|
20
|
+
convert_sdk_tool_result_block,
|
|
21
|
+
convert_sdk_tool_use_block,
|
|
22
|
+
to_sdk_variant,
|
|
23
|
+
)
|
|
3
24
|
from .messages import (
|
|
4
25
|
MessageContentBlock,
|
|
5
26
|
MessageCreateParams,
|
|
@@ -67,6 +88,26 @@ __all__ = [
|
|
|
67
88
|
"StreamEventType",
|
|
68
89
|
"ToolChoiceType",
|
|
69
90
|
"ToolType",
|
|
91
|
+
# Claude SDK models
|
|
92
|
+
"AssistantMessage",
|
|
93
|
+
"ContentBlock",
|
|
94
|
+
"ExtendedContentBlock",
|
|
95
|
+
"ResultMessage",
|
|
96
|
+
"ResultMessageBlock",
|
|
97
|
+
"SDKContentBlock",
|
|
98
|
+
"SDKMessageMode",
|
|
99
|
+
"TextBlock",
|
|
100
|
+
"ToolResultBlock",
|
|
101
|
+
"ToolResultSDKBlock",
|
|
102
|
+
"ToolUseBlock",
|
|
103
|
+
"ToolUseSDKBlock",
|
|
104
|
+
"UserMessage",
|
|
105
|
+
"convert_sdk_result_message",
|
|
106
|
+
"convert_sdk_system_message",
|
|
107
|
+
"convert_sdk_text_block",
|
|
108
|
+
"convert_sdk_tool_result_block",
|
|
109
|
+
"convert_sdk_tool_use_block",
|
|
110
|
+
"to_sdk_variant",
|
|
70
111
|
# Message models
|
|
71
112
|
"MessageContentBlock",
|
|
72
113
|
"MessageCreateParams",
|