diff --git a/src/constants.py b/src/constants.py index 5fb452b..f491336 100644 --- a/src/constants.py +++ b/src/constants.py @@ -69,9 +69,12 @@ async def chat_endpoint(): ... # Models supported by Claude Agent SDK (as of November 2025) # NOTE: Claude Agent SDK only supports Claude 4+ models, not Claude 3.x CLAUDE_MODELS = [ - # Claude 4.5 Family (Latest - Fall 2025) - RECOMMENDED - "claude-opus-4-5-20250929", # Latest Opus 4.5 - Most capable - "claude-sonnet-4-5-20250929", # Recommended - best coding model + # Claude 4.6 Family (Latest) - RECOMMENDED + "claude-opus-4-6", # Most capable + "claude-sonnet-4-6", # Recommended - best coding model + # Claude 4.5 Family (Fall 2025) + "claude-opus-4-5-20250929", + "claude-sonnet-4-5-20250929", "claude-haiku-4-5-20251001", # Fast & cheap # Claude 4.1 "claude-opus-4-1-20250805", # Upgraded Opus 4 @@ -88,7 +91,7 @@ async def chat_endpoint(): ... # Default model (recommended for most use cases) # Can be overridden via DEFAULT_MODEL environment variable -DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "claude-sonnet-4-5-20250929") +DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "claude-sonnet-4-6") # Fast model (for speed/cost optimization) FAST_MODEL = "claude-haiku-4-5-20251001" diff --git a/src/models.py b/src/models.py index 82e85f4..990fc30 100644 --- a/src/models.py +++ b/src/models.py @@ -18,26 +18,44 @@ def get_default_model(): class ContentPart(BaseModel): """Content part for multimodal messages (OpenAI format).""" - type: Literal["text"] - text: str + type: str + text: Optional[str] = None + + model_config = {"extra": "ignore"} class Message(BaseModel): - role: Literal["system", "user", "assistant"] - content: Union[str, List[ContentPart]] + role: Literal["system", "user", "assistant", "tool"] + content: Optional[Union[str, List[Any]]] = None name: Optional[str] = None + model_config = {"extra": "ignore"} + @model_validator(mode="after") def normalize_content(self): """Convert array content to string for Claude Code compatibility.""" + if self.content is None: + self.content = "" + return self + if isinstance(self.content, list): # Extract text from content parts and concatenate text_parts = [] for part in self.content: - if isinstance(part, ContentPart) and part.type == "text": + if isinstance(part, ContentPart) and part.text: text_parts.append(part.text) - elif isinstance(part, dict) and part.get("type") == "text": - text_parts.append(part.get("text", "")) + elif isinstance(part, dict): + if part.get("type") == "text" and part.get("text"): + text_parts.append(part["text"]) + elif part.get("type") == "tool_result": + # Extract text from tool result content + inner = part.get("content", "") + if isinstance(inner, str): + text_parts.append(inner) + elif isinstance(inner, list): + for block in inner: + if isinstance(block, dict) and block.get("text"): + text_parts.append(block["text"]) # Join all text parts with newlines self.content = "\n".join(text_parts) if text_parts else ""