File size: 2,492 Bytes
7419178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""
packages/shared/exceptions.py

Ultron V4 — Shared Custom Exceptions
======================================
All custom exception classes used across V4 modules live here.
Import pattern:
  from packages.shared.exceptions import AllKeysExhaustedError, SentinelKeyUnavailableError

Future bug risks (pre-registered):
  EX1 [MED] If pool.py is imported before shared/ is on PYTHONPATH, ImportError.
             Fix: ensure packages/ is in sys.path at startup (main.py responsibility).
  EX2 [LOW] New modules may define local exceptions with same name → shadowing.
             Rule: always import from shared.exceptions, never redefine locally.

Tool calls used writing this file:
  Github:push_files x1 (batch with pool.py)
"""


class UltronBaseError(Exception):
    """Base class for all Ultron V4 exceptions."""


class AllKeysExhaustedError(UltronBaseError):
    """Raised by KeyPool.get_key() when all keys in the general pool are tripped.

    Callers (task_dispatcher, llm_router) must catch this and return a 503-style
    response to Discord. Never swallow silently.

    Attributes:
        provider_states: dict of {provider: failure_count} for diagnostics.
    """

    def __init__(self, provider_states: dict | None = None):
        self.provider_states = provider_states or {}
        super().__init__(
            f"All keys in general pool exhausted. States: {self.provider_states}"
        )


class SentinelKeyUnavailableError(UltronBaseError):
    """Raised by KeyPool.get_sentinel_key() when the Sentinel Gemini key is tripped.

    Sentinel unavailability is a degraded-mode event. Ultron should:
      1. Log as CRITICAL
      2. Write incident to Notion (when Notion MCP available in Sentinel code)
      3. Continue serving user requests from general pool (Sentinel is non-user-facing)
      4. Retry Sentinel key after reset_at cooldown (24hr for Gemini).

    Attributes:
        reset_at: float UNIX timestamp when the key comes out of cooldown.
    """

    def __init__(self, reset_at: float | None = None):
        self.reset_at = reset_at
        super().__init__(
            f"Sentinel key unavailable. Cooldown until: {reset_at}"
        )


class KeyPoolConfigError(UltronBaseError):
    """Raised during KeyPool.__init__ if config is malformed.

    Fail loud at startup. Never silently skip bad config.
    """


class ProviderNotSupportedError(UltronBaseError):
    """Raised when an unrecognized provider string is encountered in llm_router."""