GitHub Viewer
"""API error classification with user-friendly messages."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
class ApiErrorCategory(Enum):
RATE_LIMIT = "rate_limit"
OVERLOADED = "overloaded"
AUTH = "auth"
GITHUB_COPILOT_AUTH = "github_copilot_auth"
CONTEXT_OVERFLOW = "context_overflow"
TIMEOUT = "timeout"
CONNECTION = "connection"
SERVER = "server"
MODEL_NOT_FOUND = "model_not_found"
CONTENT_FILTER = "content_filter"
UNKNOWN = "unknown"
# Categories that should trigger retry
_RETRYABLE = {
ApiErrorCategory.RATE_LIMIT,
ApiErrorCategory.OVERLOADED,
ApiErrorCategory.TIMEOUT,
ApiErrorCategory.CONNECTION,
ApiErrorCategory.SERVER,
}
_USER_MESSAGES = {
ApiErrorCategory.RATE_LIMIT: "Rate limit exceeded. Waiting before retrying...",
ApiErrorCategory.OVERLOADED: "API is overloaded (529). Waiting before retrying...",
ApiErrorCategory.AUTH: "Authentication failed. Check your API key (KODER_API_KEY or provider-specific key).",
ApiErrorCategory.GITHUB_COPILOT_AUTH: (
"GitHub Copilot authentication expired or failed. Run "
"`koder auth login github_copilot` to complete GitHub device login again, "
"then retry the Copilot model."
),
ApiErrorCategory.CONTEXT_OVERFLOW: "Context window exceeded. Try /compact to reduce conversation size.",
ApiErrorCategory.TIMEOUT: "Request timed out. Retrying...",
ApiErrorCategory.CONNECTION: "Connection failed. Check your network and API endpoint.",
ApiErrorCategory.SERVER: "Server error. Retrying...",
ApiErrorCategory.MODEL_NOT_FOUND: "Model not found. Check your KODER_MODEL setting. Use 'sonnet', 'opus', or 'haiku' for Claude models.",
ApiErrorCategory.CONTENT_FILTER: "Request rejected by content filter.",
ApiErrorCategory.UNKNOWN: "An unexpected error occurred.",
}
@dataclass(frozen=True)
class ClassifiedError:
category: ApiErrorCategory
user_message: str
original_error: Exception
should_retry: bool
status_code: int | None = None
def classify_api_error(
error: Exception,
status_code: int | None = None,
) -> ClassifiedError:
"""Classify an API error and return a user-friendly message."""
msg = str(error).lower()
if "github_copilot" in msg and (
"failed to refresh api key" in msg
or "copilot_internal/v2/token" in msg
or "github copilot api key" in msg
):
category = ApiErrorCategory.GITHUB_COPILOT_AUTH
# Determine category from status code first
elif status_code == 429:
category = ApiErrorCategory.RATE_LIMIT
elif status_code == 529:
category = ApiErrorCategory.OVERLOADED
elif status_code in (401, 403):
category = ApiErrorCategory.AUTH
elif status_code == 404 and ("model" in msg or "does not exist" in msg):
category = ApiErrorCategory.MODEL_NOT_FOUND
elif status_code == 404:
category = ApiErrorCategory.MODEL_NOT_FOUND
elif status_code and 500