"""Error types shared across the package.""" class TripoAPIError(Exception): """An error returned by the Tripo API (or a transport failure talking to it).""" def __init__(self, message, code=None, suggestion=None, raw=None): super().__init__(message) self.code = code self.suggestion = suggestion self.raw = raw def __str__(self): msg = f"[Tripo Error {self.code}] {super().__str__()}" if self.suggestion: msg += f" (Suggestion: {self.suggestion})" return msg @property def is_auth_error(self): """True when the JWT is missing/expired and a re-mint from the cookie could help.""" return self.code in (401, 403, 1002) class TripoTimeout(Exception): """A task did not reach a terminal state within the allotted time.""" # Signals that an account is out of credits. The exact server code is not # confirmed, so the message text is the practical trigger; extra codes can be # added without touching code via config ``insufficient_credit_codes``. INSUFFICIENT_KEYWORDS = ( "insufficient", "not enough", "no enough", "balance", "credit", "quota", "recharge", "积分", "余额", "额度不足", ) def is_insufficient_credits(error, extra_codes=()): """True when ``error`` looks like "this account has run out of credits".""" if not isinstance(error, TripoAPIError): return False if error.code is not None and error.code in set(extra_codes): return True text = str(error).lower() return any(keyword in text for keyword in INSUFFICIENT_KEYWORDS)