Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
32 changes: 25 additions & 7 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
Loading