2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Claude CLI Adapter - 基于通用 CLI Adapter 基类的简化实现
|
|
|
|
|
|
|
|
|
|
|
|
继承 CliAdapterBase,只需配置 FORMAT_ID 和 HANDLER_CLASS。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
from src.api.handlers.base.cli_adapter_base import CliAdapterBase, register_cli_adapter
|
|
|
|
|
|
from src.api.handlers.base.cli_handler_base import CliMessageHandlerBase
|
2026-03-09 18:26:53 +08:00
|
|
|
|
from src.api.handlers.claude.adapter import ClaudeCapabilityDetector
|
2025-12-19 23:40:42 +08:00
|
|
|
|
from src.config.settings import config
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from src.core.api_format import ApiFamily
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@register_cli_adapter
|
|
|
|
|
|
class ClaudeCliAdapter(CliAdapterBase):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Claude CLI API 适配器
|
|
|
|
|
|
|
|
|
|
|
|
处理 Claude CLI 格式的请求(/v1/messages 端点,使用 Bearer 认证)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
FORMAT_ID = "claude:cli"
|
|
|
|
|
|
API_FAMILY = ApiFamily.CLAUDE
|
2025-12-10 20:52:44 +08:00
|
|
|
|
name = "claude.cli"
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def HANDLER_CLASS(self) -> type[CliMessageHandlerBase]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""延迟导入 Handler 类避免循环依赖"""
|
|
|
|
|
|
from src.api.handlers.claude_cli.handler import ClaudeCliMessageHandler
|
|
|
|
|
|
|
|
|
|
|
|
return ClaudeCliMessageHandler
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def __init__(self, allowed_api_formats: list[str] | None = None):
|
2026-02-01 17:28:00 +08:00
|
|
|
|
super().__init__(allowed_api_formats)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
def detect_capability_requirements(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
headers: dict[str, str],
|
|
|
|
|
|
request_body: dict[str, Any] | None = None,
|
|
|
|
|
|
) -> dict[str, bool]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""检测 Claude CLI 请求中隐含的能力需求"""
|
2026-02-28 11:44:08 +08:00
|
|
|
|
return ClaudeCapabilityDetector.detect_from_headers(headers, request_body)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def _extract_message_count(self, payload: dict[str, Any]) -> int:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""Claude CLI 使用 messages 字段"""
|
|
|
|
|
|
messages = payload.get("messages", [])
|
|
|
|
|
|
return len(messages) if isinstance(messages, list) else 0
|
|
|
|
|
|
|
|
|
|
|
|
def _build_audit_metadata(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
payload: dict[str, Any],
|
|
|
|
|
|
path_params: dict[str, Any] | None = None, # noqa: ARG002
|
|
|
|
|
|
) -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""Claude CLI 特定的审计元数据"""
|
|
|
|
|
|
model = payload.get("model", "unknown")
|
|
|
|
|
|
stream = payload.get("stream", False)
|
|
|
|
|
|
messages = payload.get("messages", [])
|
|
|
|
|
|
|
|
|
|
|
|
role_counts = {}
|
|
|
|
|
|
for msg in messages:
|
|
|
|
|
|
role = msg.get("role", "unknown")
|
|
|
|
|
|
role_counts[role] = role_counts.get(role, 0) + 1
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"action": "claude_cli_request",
|
|
|
|
|
|
"model": model,
|
|
|
|
|
|
"stream": bool(stream),
|
|
|
|
|
|
"max_tokens": payload.get("max_tokens"),
|
|
|
|
|
|
"messages_count": len(messages),
|
|
|
|
|
|
"message_roles": role_counts,
|
|
|
|
|
|
"temperature": payload.get("temperature"),
|
|
|
|
|
|
"top_p": payload.get("top_p"),
|
|
|
|
|
|
"tool_count": len(payload.get("tools") or []),
|
|
|
|
|
|
"system_present": bool(payload.get("system")),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 00:02:56 +08:00
|
|
|
|
@classmethod
|
2026-02-01 17:28:00 +08:00
|
|
|
|
def build_endpoint_url(
|
2026-03-05 19:04:21 +08:00
|
|
|
|
cls,
|
|
|
|
|
|
base_url: str,
|
|
|
|
|
|
request_data: dict[str, Any],
|
|
|
|
|
|
model_name: str | None = None,
|
|
|
|
|
|
*,
|
|
|
|
|
|
provider_type: str | None = None,
|
2026-02-01 17:28:00 +08:00
|
|
|
|
) -> str:
|
2025-12-25 00:02:56 +08:00
|
|
|
|
"""构建Claude CLI API端点URL"""
|
|
|
|
|
|
base_url = base_url.rstrip("/")
|
|
|
|
|
|
if base_url.endswith("/v1"):
|
|
|
|
|
|
return f"{base_url}/messages"
|
|
|
|
|
|
else:
|
|
|
|
|
|
return f"{base_url}/v1/messages"
|
|
|
|
|
|
|
2026-01-27 02:17:18 +08:00
|
|
|
|
# build_request_body 使用基类实现,通过 format_conversion_registry 自动转换 OPENAI -> CLAUDE_CLI
|
2025-12-25 00:02:56 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_cli_user_agent(cls) -> str | None:
|
2025-12-25 00:02:56 +08:00
|
|
|
|
"""获取Claude CLI User-Agent"""
|
|
|
|
|
|
return config.internal_user_agent_claude_cli
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
__all__ = ["ClaudeCliAdapter"]
|