kolega-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. kolega_code/__init__.py +151 -0
  2. kolega_code/agent/__init__.py +42 -0
  3. kolega_code/agent/baseagent.py +998 -0
  4. kolega_code/agent/browseragent.py +123 -0
  5. kolega_code/agent/coder.py +157 -0
  6. kolega_code/agent/common.py +41 -0
  7. kolega_code/agent/compression.py +81 -0
  8. kolega_code/agent/context.py +112 -0
  9. kolega_code/agent/conversation.py +408 -0
  10. kolega_code/agent/generalagent.py +146 -0
  11. kolega_code/agent/investigationagent.py +123 -0
  12. kolega_code/agent/planningagent.py +187 -0
  13. kolega_code/agent/prompt_provider.py +196 -0
  14. kolega_code/agent/prompt_templates/agents/browser.j2 +102 -0
  15. kolega_code/agent/prompt_templates/agents/coder_cli_mode.j2 +127 -0
  16. kolega_code/agent/prompt_templates/agents/general.j2 +68 -0
  17. kolega_code/agent/prompt_templates/agents/investigation.j2 +72 -0
  18. kolega_code/agent/prompt_templates/common/frontend_guidance.md +36 -0
  19. kolega_code/agent/prompt_templates/common/kolega_md_instructions.md +14 -0
  20. kolega_code/agent/prompt_templates/environment_variables/workspace_env_vars.md +11 -0
  21. kolega_code/agent/prompt_templates/template_guidance/expo-template.md +379 -0
  22. kolega_code/agent/prompt_templates/template_guidance/html-website-template.md +3 -0
  23. kolega_code/agent/prompt_templates/template_guidance/mern-stack-template.md +3 -0
  24. kolega_code/agent/prompt_templates/template_guidance/react-vite-shadcdn-template.md +182 -0
  25. kolega_code/agent/prompts.py +192 -0
  26. kolega_code/agent/tests/__init__.py +0 -0
  27. kolega_code/agent/tests/llm/__init__.py +0 -0
  28. kolega_code/agent/tests/llm/test_anthropic_token_counting.py +633 -0
  29. kolega_code/agent/tests/llm/test_billing_openai_cache.py +74 -0
  30. kolega_code/agent/tests/llm/test_client.py +773 -0
  31. kolega_code/agent/tests/llm/test_dashscope_mapping.py +32 -0
  32. kolega_code/agent/tests/llm/test_error_boundary.py +322 -0
  33. kolega_code/agent/tests/llm/test_exceptions.py +249 -0
  34. kolega_code/agent/tests/llm/test_instrumented_client.py +536 -0
  35. kolega_code/agent/tests/llm/test_instrumented_client_integration.py +547 -0
  36. kolega_code/agent/tests/llm/test_langfuse_normalization.py +39 -0
  37. kolega_code/agent/tests/llm/test_model_specs.py +17 -0
  38. kolega_code/agent/tests/llm/test_openai_cached_tokens.py +58 -0
  39. kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +74 -0
  40. kolega_code/agent/tests/llm/test_openai_message_conversion.py +30 -0
  41. kolega_code/agent/tests/llm/test_openai_token_counting.py +687 -0
  42. kolega_code/agent/tests/llm/test_tool_execution_ids.py +193 -0
  43. kolega_code/agent/tests/services/__init__.py +1 -0
  44. kolega_code/agent/tests/services/test_browser.py +447 -0
  45. kolega_code/agent/tests/services/test_browser_parity.py +353 -0
  46. kolega_code/agent/tests/services/test_file_system.py +699 -0
  47. kolega_code/agent/tests/services/test_sandbox_terminal_input.py +98 -0
  48. kolega_code/agent/tests/services/test_terminal.py +154 -0
  49. kolega_code/agent/tests/services/test_terminal_command_tracking.py +385 -0
  50. kolega_code/agent/tests/services/test_terminal_state_serializer.py +262 -0
  51. kolega_code/agent/tests/test_agent_tools_inventory.py +267 -0
  52. kolega_code/agent/tests/test_base_agent.py +1942 -0
  53. kolega_code/agent/tests/test_coder_attachments.py +330 -0
  54. kolega_code/agent/tests/test_coder_prompt_extensions.py +61 -0
  55. kolega_code/agent/tests/test_commands.py +179 -0
  56. kolega_code/agent/tests/test_duplicate_tool_results.py +556 -0
  57. kolega_code/agent/tests/test_empty_message_handling.py +48 -0
  58. kolega_code/agent/tests/test_general_agent.py +242 -0
  59. kolega_code/agent/tests/test_html.py +320 -0
  60. kolega_code/agent/tests/test_parallel_tool_calls.py +291 -0
  61. kolega_code/agent/tests/test_planning_agent.py +227 -0
  62. kolega_code/agent/tests/test_prompt_provider.py +271 -0
  63. kolega_code/agent/tests/test_tool_registry.py +102 -0
  64. kolega_code/agent/tests/test_tools.py +549 -0
  65. kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  66. kolega_code/agent/tests/tool_backend/test_agent_tool.py +356 -0
  67. kolega_code/agent/tests/tool_backend/test_base_tool.py +147 -0
  68. kolega_code/agent/tests/tool_backend/test_browser_tool.py +335 -0
  69. kolega_code/agent/tests/tool_backend/test_build_tool.py +93 -0
  70. kolega_code/agent/tests/tool_backend/test_create_file_tool.py +115 -0
  71. kolega_code/agent/tests/tool_backend/test_glob_tool.py +196 -0
  72. kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +230 -0
  73. kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +292 -0
  74. kolega_code/agent/tests/tool_backend/test_read_file_tool.py +173 -0
  75. kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +115 -0
  76. kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +141 -0
  77. kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +174 -0
  78. kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +228 -0
  79. kolega_code/agent/tests/tool_backend/test_terminal_tool.py +482 -0
  80. kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +189 -0
  81. kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +445 -0
  82. kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +194 -0
  83. kolega_code/agent/tool_backend/agent_tool.py +414 -0
  84. kolega_code/agent/tool_backend/apply_edit_tool.py +98 -0
  85. kolega_code/agent/tool_backend/apply_patch_tool.py +514 -0
  86. kolega_code/agent/tool_backend/base_tool.py +217 -0
  87. kolega_code/agent/tool_backend/browser_tool.py +271 -0
  88. kolega_code/agent/tool_backend/build_tool.py +93 -0
  89. kolega_code/agent/tool_backend/create_file_tool.py +52 -0
  90. kolega_code/agent/tool_backend/glob_tool.py +323 -0
  91. kolega_code/agent/tool_backend/list_directory_tool.py +300 -0
  92. kolega_code/agent/tool_backend/memory_tool.py +79 -0
  93. kolega_code/agent/tool_backend/read_file_tool.py +119 -0
  94. kolega_code/agent/tool_backend/replace_entire_file_tool.py +40 -0
  95. kolega_code/agent/tool_backend/replace_lines_tool.py +97 -0
  96. kolega_code/agent/tool_backend/search_and_replace_tool.py +146 -0
  97. kolega_code/agent/tool_backend/search_codebase_tool.py +377 -0
  98. kolega_code/agent/tool_backend/streaming_tool.py +47 -0
  99. kolega_code/agent/tool_backend/terminal_tool.py +643 -0
  100. kolega_code/agent/tool_backend/think_hard_tool.py +211 -0
  101. kolega_code/agent/tool_backend/web_fetch_tool.py +205 -0
  102. kolega_code/agent/tools.py +1704 -0
  103. kolega_code/agent/utils/commands.py +94 -0
  104. kolega_code/cli/__init__.py +1 -0
  105. kolega_code/cli/app.py +2756 -0
  106. kolega_code/cli/config.py +280 -0
  107. kolega_code/cli/connection.py +49 -0
  108. kolega_code/cli/file_index.py +147 -0
  109. kolega_code/cli/main.py +564 -0
  110. kolega_code/cli/mentions.py +155 -0
  111. kolega_code/cli/messages.py +89 -0
  112. kolega_code/cli/provider_registry.py +96 -0
  113. kolega_code/cli/session_store.py +207 -0
  114. kolega_code/cli/settings.py +87 -0
  115. kolega_code/cli/skills.py +409 -0
  116. kolega_code/cli/slash_commands.py +108 -0
  117. kolega_code/cli/tests/__init__.py +1 -0
  118. kolega_code/cli/tests/test_app.py +4251 -0
  119. kolega_code/cli/tests/test_cli_config.py +171 -0
  120. kolega_code/cli/tests/test_connection.py +26 -0
  121. kolega_code/cli/tests/test_file_index.py +103 -0
  122. kolega_code/cli/tests/test_main.py +455 -0
  123. kolega_code/cli/tests/test_mentions.py +108 -0
  124. kolega_code/cli/tests/test_session_store.py +67 -0
  125. kolega_code/cli/tests/test_settings.py +62 -0
  126. kolega_code/cli/tests/test_skills.py +157 -0
  127. kolega_code/cli/tests/test_slash_commands.py +88 -0
  128. kolega_code/cli/theme.py +180 -0
  129. kolega_code/config.py +154 -0
  130. kolega_code/events.py +202 -0
  131. kolega_code/llm/client.py +300 -0
  132. kolega_code/llm/exceptions.py +285 -0
  133. kolega_code/llm/instrumented_client.py +520 -0
  134. kolega_code/llm/models.py +1368 -0
  135. kolega_code/llm/providers/__init__.py +0 -0
  136. kolega_code/llm/providers/anthropic.py +387 -0
  137. kolega_code/llm/providers/base.py +71 -0
  138. kolega_code/llm/providers/google.py +157 -0
  139. kolega_code/llm/providers/models.py +37 -0
  140. kolega_code/llm/providers/openai.py +363 -0
  141. kolega_code/llm/ratelimit.py +40 -0
  142. kolega_code/llm/specs.py +67 -0
  143. kolega_code/llm/tool_execution_ids.py +18 -0
  144. kolega_code/models/__init__.py +9 -0
  145. kolega_code/models/sandbox_terminal_state.py +47 -0
  146. kolega_code/runtime.py +50 -0
  147. kolega_code/sandbox/README.md +200 -0
  148. kolega_code/sandbox/__init__.py +21 -0
  149. kolega_code/sandbox/async_filesystem.py +475 -0
  150. kolega_code/sandbox/base.py +297 -0
  151. kolega_code/sandbox/browser.py +25 -0
  152. kolega_code/sandbox/event_loop.py +43 -0
  153. kolega_code/sandbox/filesystem.py +341 -0
  154. kolega_code/sandbox/local.py +118 -0
  155. kolega_code/sandbox/serializer.py +175 -0
  156. kolega_code/sandbox/terminal.py +868 -0
  157. kolega_code/sandbox/utils.py +216 -0
  158. kolega_code/services/base.py +255 -0
  159. kolega_code/services/browser.py +444 -0
  160. kolega_code/services/file_system.py +749 -0
  161. kolega_code/services/html.py +221 -0
  162. kolega_code/services/terminal.py +903 -0
  163. kolega_code/tools/__init__.py +22 -0
  164. kolega_code/tools/core.py +33 -0
  165. kolega_code/tools/definitions.py +81 -0
  166. kolega_code/tools/registry.py +73 -0
  167. kolega_code-0.1.0.dist-info/METADATA +157 -0
  168. kolega_code-0.1.0.dist-info/RECORD +171 -0
  169. kolega_code-0.1.0.dist-info/WHEEL +4 -0
  170. kolega_code-0.1.0.dist-info/entry_points.txt +2 -0
  171. kolega_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,285 @@
1
+ """
2
+ This module defines custom exception classes for handling errors related to
3
+ Large Language Model (LLM) interactions across different providers. It provides a
4
+ base `LLMError` class and specific subclasses for common error scenarios.
5
+ Additionally, it includes utility functions to map provider-specific errors
6
+ (OpenAI, Google, Anthropic) to these standardized exceptions.
7
+
8
+ Error Mapping:
9
+
10
+ | Provider | Status Code | Mapped LLMError |
11
+ |-----------|-------------|--------------------------------|
12
+ | OpenAI | 400 | `LLMInvalidRequestError` |
13
+ | OpenAI | 401 | `LLMAuthenticationError` |
14
+ | OpenAI | 403 | `LLMPermissionDeniedError` |
15
+ | OpenAI | 404 | `LLMNotFoundError` |
16
+ | OpenAI | 422 | `LLMUnprocessableEntityError` |
17
+ | OpenAI | 429 | `LLMRateLimitError` |
18
+ | OpenAI | 500 | `LLMInternalServerError` |
19
+ | OpenAI | Other | `LLMError` |
20
+ | Google | 400 | `LLMInvalidRequestError` |
21
+ | Google | 403 | `LLMPermissionDeniedError` |
22
+ | Google | 429 | `LLMRateLimitError` |
23
+ | Google | 500 | `LLMInternalServerError` |
24
+ | Google | Other | `LLMError` |
25
+ | Anthropic | 400 | `LLMInvalidRequestError` |
26
+ | Anthropic | 401 | `LLMAuthenticationError` |
27
+ | Anthropic | 403 | `LLMPermissionDeniedError` |
28
+ | Anthropic | 404 | `LLMNotFoundError` |
29
+ | Anthropic | 413 | `LLMContextWindowExceededError`|
30
+ | Anthropic | 429 | `LLMRateLimitError` |
31
+ | Anthropic | 500 | `LLMInternalServerError` |
32
+ | Anthropic | 529 | `LLMInternalServerError` |
33
+ | Anthropic | Other | `LLMError` |
34
+
35
+ """
36
+
37
+ import asyncio
38
+
39
+ from anthropic import (
40
+ AnthropicError,
41
+ APIStatusError as AnthropicAPIStatusError,
42
+ InternalServerError as AnthropicInternalServerError,
43
+ )
44
+ from google.genai.errors import APIError as GoogleAPIError
45
+ from openai import OpenAIError
46
+
47
+ try:
48
+ import aiohttp
49
+ except ImportError:
50
+ aiohttp = None # Handle case where aiohttp not installed
51
+
52
+ # Handle httpx being optional. If installed, we will map certain httpx errors.
53
+ try:
54
+ import httpx
55
+ except ImportError:
56
+ httpx = None
57
+
58
+ from ..config import ModelProvider
59
+
60
+
61
+ class LLMError(Exception):
62
+ """Base exception class for all LLM-related errors."""
63
+
64
+ def __init__(self, message: str, model: str = None, provider: str = None):
65
+ super().__init__(message)
66
+ self.provider = provider
67
+
68
+
69
+ class LLMBadRequestError(LLMError):
70
+ """Raised when the request to the LLM service is malformed or invalid."""
71
+
72
+
73
+ class LLMUnsupportedParamsError(LLMError):
74
+ """Raised when unsupported parameters are provided to the LLM service."""
75
+
76
+
77
+ class LLMContextWindowExceededError(LLMError):
78
+ """Raised when the input exceeds the model's maximum context window size."""
79
+
80
+
81
+ class LLMContentPolicyViolationError(LLMError):
82
+ """Raised when the request violates the LLM provider's content policy."""
83
+
84
+
85
+ class LLMInvalidRequestError(LLMError):
86
+ """Raised when the request is invalid for reasons other than malformed data."""
87
+
88
+
89
+ class LLMAuthenticationError(LLMError):
90
+ """Raised when authentication with the LLM service fails."""
91
+
92
+
93
+ class LLMPermissionDeniedError(LLMError):
94
+ """Raised when the authenticated user lacks permission for the requested operation."""
95
+
96
+
97
+ class LLMNotFoundError(LLMError):
98
+ """Raised when the requested resource is not found."""
99
+
100
+
101
+ class LLMTimeout(LLMError):
102
+ """Raised when the request to the LLM service times out."""
103
+
104
+
105
+ class LLMUnprocessableEntityError(LLMError):
106
+ """Raised when the request is well-formed but cannot be processed."""
107
+
108
+
109
+ class LLMRateLimitError(LLMError):
110
+ """Raised when the rate limit for the LLM service is exceeded."""
111
+
112
+
113
+ class LLMInternalServerError(LLMError):
114
+ """Raised when the LLM service encounters an internal error."""
115
+
116
+
117
+ def map_openai_errors(error: OpenAIError) -> LLMError:
118
+ if hasattr(error, "status_code"):
119
+ if error.status_code == 400:
120
+ return LLMInvalidRequestError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
121
+ elif error.status_code == 401:
122
+ return LLMAuthenticationError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
123
+ elif error.status_code == 403:
124
+ return LLMPermissionDeniedError(
125
+ message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value
126
+ )
127
+ elif error.status_code == 404:
128
+ return LLMNotFoundError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
129
+ elif error.status_code == 422:
130
+ return LLMUnprocessableEntityError(
131
+ message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value
132
+ )
133
+ elif error.status_code == 429:
134
+ return LLMRateLimitError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
135
+ elif error.status_code == 500:
136
+ return LLMInternalServerError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
137
+
138
+ return LLMError(message=f"OpenAI APIError: {str(error)}", provider=ModelProvider.OPENAI.value)
139
+
140
+
141
+ def map_google_errors(error: GoogleAPIError) -> LLMError:
142
+ if hasattr(error, "status"):
143
+
144
+ if error.status == 400:
145
+ return LLMInvalidRequestError(message=f"GoogleAPIError: {str(error)}", provider=ModelProvider.GOOGLE.value)
146
+ elif error.status == 403:
147
+ return LLMPermissionDeniedError(
148
+ message=f"GoogleAPIError: {str(error)}", provider=ModelProvider.GOOGLE.value
149
+ )
150
+ elif error.status == 429:
151
+ return LLMRateLimitError(message=f"GoogleAPIError: {str(error)}", provider=ModelProvider.GOOGLE.value)
152
+ elif error.status == 500:
153
+ return LLMInternalServerError(message=f"GoogleAPIError: {str(error)}", provider=ModelProvider.GOOGLE.value)
154
+
155
+ return LLMError(message=f"Google APIError: {str(error)}", provider=ModelProvider.GOOGLE.value)
156
+
157
+
158
+ def map_anthropic_errors(error: AnthropicError) -> LLMError:
159
+ context_window_phrases = (
160
+ "exceeded model token limit",
161
+ "context window",
162
+ "maximum context length",
163
+ "prompt is too long",
164
+ )
165
+
166
+ if type(error) == AnthropicAPIStatusError:
167
+ try:
168
+ error_data = error.body
169
+ if isinstance(error_data, dict) and "error" in error_data:
170
+ error_info = error_data["error"]
171
+ error_type = error_info.get("type")
172
+ error_message = (error_info.get("message") or "").strip()
173
+ error_message_lower = error_message.lower()
174
+
175
+ # Keep internal/server overload handling by type
176
+ if error_type in ["overloaded_error", "api_error"]:
177
+ return LLMInternalServerError(
178
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
179
+ )
180
+
181
+ if any(phrase in error_message_lower for phrase in context_window_phrases):
182
+ return LLMContextWindowExceededError(
183
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
184
+ )
185
+
186
+ # Special case: content filtering block should be mapped to content policy violation
187
+ if (
188
+ error_type == "invalid_request_error"
189
+ and error_message == "Output blocked by content filtering policy"
190
+ ):
191
+ return LLMContentPolicyViolationError(
192
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
193
+ )
194
+
195
+ except Exception:
196
+ pass
197
+
198
+ if type(error) == AnthropicInternalServerError:
199
+ return LLMInternalServerError(message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value)
200
+
201
+ print("continuing Anthropic error mapping...")
202
+ if hasattr(error, "status_code"):
203
+ if error.status_code == 400:
204
+ error_text = str(error).lower()
205
+ if any(phrase in error_text for phrase in context_window_phrases):
206
+ return LLMContextWindowExceededError(
207
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
208
+ )
209
+ return LLMInvalidRequestError(
210
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
211
+ )
212
+ elif error.status_code == 401:
213
+ return LLMAuthenticationError(
214
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
215
+ )
216
+ elif error.status_code == 403:
217
+ return LLMPermissionDeniedError(
218
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
219
+ )
220
+ elif error.status_code == 404:
221
+ return LLMNotFoundError(message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value)
222
+ elif error.status_code == 413:
223
+ return LLMContextWindowExceededError(
224
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
225
+ )
226
+ if error.status_code == 429:
227
+ return LLMRateLimitError(message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value)
228
+ if error.status_code == 500:
229
+ return LLMInternalServerError(
230
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
231
+ )
232
+ if error.status_code == 529:
233
+ return LLMInternalServerError(
234
+ message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value
235
+ )
236
+
237
+ return LLMError(message=f"AnthropicError: {str(error)}", provider=ModelProvider.ANTHROPIC.value)
238
+
239
+
240
+ def map_to_llm_error(error: Exception, provider: str = None) -> LLMError:
241
+ """Map any exception to a standardized LLM error.
242
+
243
+ This function provides a single point of control for converting any exception
244
+ type into an appropriate LLMError subclass. It ensures that only LLMError
245
+ exceptions escape from the LLM client layer.
246
+
247
+ Args:
248
+ error: The exception to map
249
+ provider: Optional provider name for context
250
+
251
+ Returns:
252
+ An appropriate LLMError subclass instance
253
+ """
254
+ # If already an LLM error, return as-is
255
+ if isinstance(error, LLMError):
256
+ return error
257
+
258
+ # Map provider-specific errors using existing functions
259
+ if isinstance(error, OpenAIError):
260
+ return map_openai_errors(error)
261
+ elif isinstance(error, GoogleAPIError):
262
+ return map_google_errors(error)
263
+ elif isinstance(error, AnthropicError):
264
+ return map_anthropic_errors(error)
265
+
266
+ # Map common Python exceptions
267
+ if isinstance(error, ValueError):
268
+ return LLMInvalidRequestError(message=f"Invalid parameter: {str(error)}", provider=provider)
269
+ elif isinstance(error, (TimeoutError, asyncio.TimeoutError)):
270
+ return LLMTimeout(message=f"Request timeout: {str(error)}", provider=provider)
271
+ elif isinstance(error, ConnectionError):
272
+ return LLMInternalServerError(message=f"Connection error: {str(error)}", provider=provider)
273
+ elif aiohttp and isinstance(error, aiohttp.ClientError):
274
+ return LLMInternalServerError(message=f"HTTP client error: {str(error)}", provider=provider)
275
+ elif httpx and isinstance(error, httpx.RemoteProtocolError):
276
+ return LLMInternalServerError(message=f"HTTPX protocol error: {str(error)}", provider=provider)
277
+ elif isinstance(error, KeyError):
278
+ return LLMInvalidRequestError(message=f"Missing required parameter: {str(error)}", provider=provider)
279
+ elif isinstance(error, TypeError):
280
+ return LLMInvalidRequestError(message=f"Invalid parameter type: {str(error)}", provider=provider)
281
+ elif isinstance(error, RuntimeError):
282
+ return LLMInternalServerError(message=f"Runtime error: {str(error)}", provider=provider)
283
+
284
+ # Default fallback for any other exception
285
+ return LLMError(message=f"Unexpected error ({type(error).__name__}): {str(error)}", provider=provider)