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.
- 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 +62 -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 +76 -29
- ccproxy/claude_sdk/parser.py +200 -0
- ccproxy/claude_sdk/streaming.py +286 -0
- ccproxy/cli/commands/__init__.py +5 -2
- ccproxy/cli/commands/auth.py +2 -4
- ccproxy/cli/commands/permission_handler.py +553 -0
- ccproxy/cli/commands/serve.py +30 -12
- ccproxy/cli/docker/params.py +0 -4
- ccproxy/cli/helpers.py +0 -2
- ccproxy/cli/main.py +5 -16
- ccproxy/cli/options/claude_options.py +19 -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 +13 -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 +29 -2
- 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 +220 -328
- 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.2.dist-info → ccproxy_api-0.1.3.dist-info}/METADATA +63 -2
- ccproxy_api-0.1.3.dist-info/RECORD +166 -0
- ccproxy/cli/commands/permission.py +0 -128
- ccproxy_api-0.1.2.dist-info/RECORD +0 -150
- /ccproxy/scheduler/{exceptions.py → errors.py} +0 -0
- {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/WHEEL +0 -0
- {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/entry_points.txt +0 -0
- {ccproxy_api-0.1.2.dist-info → ccproxy_api-0.1.3.dist-info}/licenses/LICENSE +0 -0
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",
|