Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-skills/src/lexigram/ai/skills/exceptions.py: 63%

38 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Exceptions for lexigram-ai-skills.""" 

2 

3from __future__ import annotations 

4 

5from lexigram.contracts.ai.skills import SkillError 

6 

7 

8class SkillNotFoundError(SkillError): 

9 """Raised when a requested skill is not registered.""" 

10 

11 _code: str = "LEX_ERR_SKILL_002" 

12 

13 def __init__(self, skill_name: str) -> None: 

14 """Initialise with the missing skill name. 

15 

16 Args: 

17 skill_name: Name of the skill that was not found. 

18 """ 

19 super().__init__(f"Skill not found: {skill_name!r}", skill_name=skill_name) 

20 self.skill_name = skill_name 

21 

22 

23class SkillAlreadyRegisteredError(SkillError): 

24 """Raised when a skill is registered under a name that already exists.""" 

25 

26 _code: str = "LEX_ERR_SKILL_003" 

27 

28 def __init__(self, skill_name: str) -> None: 

29 """Initialise with the duplicate skill name. 

30 

31 Args: 

32 skill_name: Name of the skill that was already registered. 

33 """ 

34 super().__init__( 

35 f"Skill already registered: {skill_name!r}", skill_name=skill_name 

36 ) 

37 

38 

39class SkillValidationError(SkillError): 

40 """Raised when skill parameter validation fails.""" 

41 

42 _code: str = "LEX_ERR_SKILL_004" 

43 

44 def __init__(self, skill_name: str, errors: list[str]) -> None: 

45 """Initialise with validation error details. 

46 

47 Args: 

48 skill_name: Name of the skill that failed validation. 

49 errors: List of human-readable validation error messages. 

50 """ 

51 super().__init__( 

52 f"Validation failed for {skill_name!r}: {'; '.join(errors)}", 

53 skill_name=skill_name, 

54 ) 

55 self.errors = errors 

56 

57 

58class SkillPermissionDeniedError(SkillError): 

59 """Raised when the caller lacks required permissions for a skill.""" 

60 

61 _code: str = "LEX_ERR_SKILL_005" 

62 

63 def __init__(self, skill_name: str, required: list[str]) -> None: 

64 """Initialise with permission details. 

65 

66 Args: 

67 skill_name: Name of the skill that requires permissions. 

68 required: Required permission strings. 

69 """ 

70 super().__init__( 

71 f"Permission denied for {skill_name!r}. Required: {required}", 

72 skill_name=skill_name, 

73 ) 

74 self.required = required 

75 

76 

77class SkillTimeoutError(SkillError): 

78 """Raised when skill execution exceeds the configured timeout.""" 

79 

80 _code: str = "LEX_ERR_SKILL_006" 

81 

82 def __init__(self, skill_name: str, timeout_seconds: float) -> None: 

83 """Initialise with timeout context. 

84 

85 Args: 

86 skill_name: Name of the skill that timed out. 

87 timeout_seconds: The timeout that was exceeded. 

88 """ 

89 super().__init__( 

90 f"Skill {skill_name!r} timed out after {timeout_seconds}s", 

91 skill_name=skill_name, 

92 ) 

93 self.skill_name = skill_name 

94 self.timeout_seconds = timeout_seconds 

95 

96 

97class SkillRoutingError(SkillError): 

98 """Raised when a SkillRouter finds no matching route.""" 

99 

100 _code: str = "LEX_ERR_SKILL_007" 

101 

102 def __init__(self, message: str = "No matching route") -> None: 

103 """Initialise with an optional detail message. 

104 

105 Args: 

106 message: Human-readable description of the routing failure. 

107 """ 

108 super().__init__(message) 

109 

110 

111class SkillExecutionError(SkillError): 

112 """Raised when a skill execution fails after all retry attempts.""" 

113 

114 _code: str = "LEX_ERR_SKILL_008" 

115 

116 def __init__( 

117 self, 

118 message: str, 

119 cause: Exception | None = None, 

120 skill_name: str | None = None, 

121 ) -> None: 

122 """Initialise with an error message and optional cause. 

123 

124 Args: 

125 message: Human-readable error description. 

126 cause: The underlying exception, if any. 

127 skill_name: Name of the skill that failed (optional). 

128 """ 

129 super().__init__(message, skill_name=skill_name) 

130 self.skill_name = skill_name 

131 self.cause = cause 

132 

133 

134__all__ = [ 

135 "SkillAlreadyRegisteredError", 

136 "SkillExecutionError", 

137 "SkillNotFoundError", 

138 "SkillPermissionDeniedError", 

139 "SkillRoutingError", 

140 "SkillTimeoutError", 

141 "SkillValidationError", 

142]