192 lines
No EOL
8.1 KiB
Python
192 lines
No EOL
8.1 KiB
Python
"""
|
|
Parameter validation and mapping utilities for OpenAI to Claude Code SDK conversion.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from models import ChatCompletionRequest
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ParameterValidator:
|
|
"""Validates and maps OpenAI Chat Completions parameters to Claude Code SDK options."""
|
|
|
|
# Supported Claude Code SDK models
|
|
SUPPORTED_MODELS = {
|
|
"claude-sonnet-4-20250514",
|
|
"claude-opus-4-20250514",
|
|
"claude-3-7-sonnet-20250219",
|
|
"claude-3-5-sonnet-20241022",
|
|
"claude-3-5-haiku-20241022"
|
|
}
|
|
|
|
# Valid permission modes for Claude Code SDK
|
|
VALID_PERMISSION_MODES = {"default", "acceptEdits", "bypassPermissions"}
|
|
|
|
@classmethod
|
|
def validate_model(cls, model: str) -> bool:
|
|
"""Validate that the model is supported by Claude Code SDK."""
|
|
if model not in cls.SUPPORTED_MODELS:
|
|
logger.warning(f"Model '{model}' may not be supported by Claude Code SDK. Supported models: {cls.SUPPORTED_MODELS}")
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def validate_permission_mode(cls, permission_mode: str) -> bool:
|
|
"""Validate permission mode parameter."""
|
|
if permission_mode not in cls.VALID_PERMISSION_MODES:
|
|
logger.error(f"Invalid permission_mode '{permission_mode}'. Valid options: {cls.VALID_PERMISSION_MODES}")
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def validate_tools(cls, tools: List[str]) -> bool:
|
|
"""Validate tool names (basic validation for non-empty strings)."""
|
|
if not all(isinstance(tool, str) and tool.strip() for tool in tools):
|
|
logger.error("All tool names must be non-empty strings")
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def create_enhanced_options(
|
|
cls,
|
|
request: ChatCompletionRequest,
|
|
max_turns: Optional[int] = None,
|
|
allowed_tools: Optional[List[str]] = None,
|
|
disallowed_tools: Optional[List[str]] = None,
|
|
permission_mode: Optional[str] = None,
|
|
max_thinking_tokens: Optional[int] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Create enhanced Claude Code SDK options with additional parameters.
|
|
|
|
This allows API users to pass Claude-Code-specific parameters that don't
|
|
exist in the OpenAI API through custom headers or environment variables.
|
|
"""
|
|
# Start with basic options from request
|
|
options = request.to_claude_options()
|
|
|
|
# Add Claude Code SDK specific options
|
|
if max_turns is not None:
|
|
if max_turns < 1 or max_turns > 100:
|
|
logger.warning(f"max_turns={max_turns} is outside recommended range (1-100)")
|
|
options['max_turns'] = max_turns
|
|
|
|
if allowed_tools:
|
|
if cls.validate_tools(allowed_tools):
|
|
options['allowed_tools'] = allowed_tools
|
|
|
|
if disallowed_tools:
|
|
if cls.validate_tools(disallowed_tools):
|
|
options['disallowed_tools'] = disallowed_tools
|
|
|
|
if permission_mode:
|
|
if cls.validate_permission_mode(permission_mode):
|
|
options['permission_mode'] = permission_mode
|
|
|
|
if max_thinking_tokens is not None:
|
|
if max_thinking_tokens < 0 or max_thinking_tokens > 50000:
|
|
logger.warning(f"max_thinking_tokens={max_thinking_tokens} is outside recommended range (0-50000)")
|
|
options['max_thinking_tokens'] = max_thinking_tokens
|
|
|
|
return options
|
|
|
|
@classmethod
|
|
def extract_claude_headers(cls, headers: Dict[str, str]) -> Dict[str, Any]:
|
|
"""
|
|
Extract Claude-Code-specific parameters from custom HTTP headers.
|
|
|
|
This allows clients to pass SDK-specific options via headers:
|
|
- X-Claude-Max-Turns: 5
|
|
- X-Claude-Allowed-Tools: tool1,tool2,tool3
|
|
- X-Claude-Permission-Mode: acceptEdits
|
|
"""
|
|
claude_options = {}
|
|
|
|
# Extract max_turns
|
|
if 'x-claude-max-turns' in headers:
|
|
try:
|
|
claude_options['max_turns'] = int(headers['x-claude-max-turns'])
|
|
except ValueError:
|
|
logger.warning(f"Invalid X-Claude-Max-Turns header: {headers['x-claude-max-turns']}")
|
|
|
|
# Extract allowed tools
|
|
if 'x-claude-allowed-tools' in headers:
|
|
tools = [tool.strip() for tool in headers['x-claude-allowed-tools'].split(',')]
|
|
if tools:
|
|
claude_options['allowed_tools'] = tools
|
|
|
|
# Extract disallowed tools
|
|
if 'x-claude-disallowed-tools' in headers:
|
|
tools = [tool.strip() for tool in headers['x-claude-disallowed-tools'].split(',')]
|
|
if tools:
|
|
claude_options['disallowed_tools'] = tools
|
|
|
|
# Extract permission mode
|
|
if 'x-claude-permission-mode' in headers:
|
|
claude_options['permission_mode'] = headers['x-claude-permission-mode']
|
|
|
|
# Extract max thinking tokens
|
|
if 'x-claude-max-thinking-tokens' in headers:
|
|
try:
|
|
claude_options['max_thinking_tokens'] = int(headers['x-claude-max-thinking-tokens'])
|
|
except ValueError:
|
|
logger.warning(f"Invalid X-Claude-Max-Thinking-Tokens header: {headers['x-claude-max-thinking-tokens']}")
|
|
|
|
return claude_options
|
|
|
|
|
|
class CompatibilityReporter:
|
|
"""Reports on OpenAI API compatibility and suggests alternatives."""
|
|
|
|
@classmethod
|
|
def generate_compatibility_report(cls, request: ChatCompletionRequest) -> Dict[str, Any]:
|
|
"""Generate a detailed compatibility report for the request."""
|
|
report = {
|
|
"supported_parameters": [],
|
|
"unsupported_parameters": [],
|
|
"warnings": [],
|
|
"suggestions": []
|
|
}
|
|
|
|
# Check supported parameters
|
|
if request.model:
|
|
report["supported_parameters"].append("model")
|
|
if request.messages:
|
|
report["supported_parameters"].append("messages")
|
|
if request.stream is not None:
|
|
report["supported_parameters"].append("stream")
|
|
if request.user:
|
|
report["supported_parameters"].append("user (for logging)")
|
|
|
|
# Check unsupported parameters with suggestions
|
|
if request.temperature != 1.0:
|
|
report["unsupported_parameters"].append("temperature")
|
|
report["suggestions"].append("Claude Code SDK does not support temperature control. Consider using different models for varied response styles (e.g., claude-3-5-haiku for more focused responses).")
|
|
|
|
if request.top_p != 1.0:
|
|
report["unsupported_parameters"].append("top_p")
|
|
report["suggestions"].append("Claude Code SDK does not support top_p. This parameter will be ignored.")
|
|
|
|
if request.max_tokens:
|
|
report["unsupported_parameters"].append("max_tokens")
|
|
report["suggestions"].append("Use max_turns parameter instead to limit conversation length, or use max_thinking_tokens to limit internal reasoning.")
|
|
|
|
if request.n > 1:
|
|
report["unsupported_parameters"].append("n")
|
|
report["suggestions"].append("Claude Code SDK only supports single responses (n=1). For multiple variations, make separate API calls.")
|
|
|
|
if request.stop:
|
|
report["unsupported_parameters"].append("stop")
|
|
report["suggestions"].append("Stop sequences are not supported. Consider post-processing responses or using max_turns to limit output.")
|
|
|
|
if request.presence_penalty != 0 or request.frequency_penalty != 0:
|
|
report["unsupported_parameters"].extend(["presence_penalty", "frequency_penalty"])
|
|
report["suggestions"].append("Penalty parameters are not supported. Consider using different system prompts to encourage varied responses.")
|
|
|
|
if request.logit_bias:
|
|
report["unsupported_parameters"].append("logit_bias")
|
|
report["suggestions"].append("Logit bias is not supported. Consider using system prompts to guide response style.")
|
|
|
|
return report |