claude-code-openai-wrapper/parameter_validator.py
2025-08-11 20:22:47 +05:30

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