omnimancer-cli 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 (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,711 @@
1
+ """
2
+ AWS Bedrock provider implementation for Omnimancer.
3
+
4
+ This module provides the AWS Bedrock provider implementation using AWS Bedrock's Converse API
5
+ with support for Claude models, API key authentication, and region configuration.
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime
10
+ from typing import Dict, List
11
+
12
+ import httpx
13
+
14
+ from ..core.models import (
15
+ ChatContext,
16
+ ChatResponse,
17
+ EnhancedModelInfo,
18
+ ToolCall,
19
+ ToolDefinition,
20
+ )
21
+ from ..utils.errors import (
22
+ AuthenticationError,
23
+ ModelNotFoundError,
24
+ NetworkError,
25
+ ProviderError,
26
+ RateLimitError,
27
+ )
28
+ from .base import BaseProvider
29
+
30
+
31
+ class BedrockProvider(BaseProvider):
32
+ """
33
+ AWS Bedrock provider implementation using AWS Bedrock's Converse API.
34
+
35
+ Supports Claude models through Bedrock, API key authentication,
36
+ and region configuration.
37
+ """
38
+
39
+ def __init__(self, api_key: str, model: str = "", **kwargs):
40
+ """
41
+ Initialize AWS Bedrock provider.
42
+
43
+ Args:
44
+ api_key: AWS Bedrock API key (30-day key from AWS console)
45
+ model: Bedrock model ID or ARN to use (e.g., 'anthropic.claude-3-sonnet-20240229-v1:0'
46
+ or 'arn:aws:bedrock:us-west-2::foundation-model/meta.llama4-maverick-17b-instruct-v1:0')
47
+ **kwargs: Additional configuration including region settings
48
+ """
49
+ # Validate API key is provided
50
+ if not api_key:
51
+ raise ValueError("API key is required for AWS Bedrock provider")
52
+
53
+ # Validate model is provided (Bedrock requires explicit model specification)
54
+ if not model:
55
+ raise ValueError(
56
+ "Model ID is required for AWS Bedrock provider. "
57
+ "Use a model ID (e.g., 'anthropic.claude-3-sonnet-20240229-v1:0') "
58
+ "or inference profile (e.g., 'us.anthropic.claude-3-sonnet-20240229-v1:0')"
59
+ )
60
+
61
+ super().__init__(api_key, model, **kwargs)
62
+
63
+ # AWS-specific configuration
64
+ self.aws_region = kwargs.get("aws_region", "us-east-1")
65
+
66
+ # Standard parameters
67
+ self.max_tokens = kwargs.get("max_tokens", 4096)
68
+ self.temperature = kwargs.get("temperature", 0.7)
69
+ self.top_p = kwargs.get("top_p", 1.0)
70
+ self.top_k = kwargs.get("top_k", 250)
71
+
72
+ # Build base URL for Bedrock API key authentication
73
+ self.base_url = f"https://bedrock-runtime.{self.aws_region}.amazonaws.com"
74
+
75
+ def _is_arn(self, model_id: str) -> bool:
76
+ """Check if the model ID is in ARN format."""
77
+ return model_id.startswith("arn:aws:bedrock:")
78
+
79
+ def _extract_model_id_from_arn(self, arn: str) -> str:
80
+ """Extract the model ID from an ARN."""
81
+ if self._is_arn(arn):
82
+ # ARN format: arn:aws:bedrock:region::foundation-model/model-id
83
+ # or: arn:aws:bedrock:region::inference-profile/profile-id
84
+ return arn.split("/")[-1]
85
+ return arn
86
+
87
+ def _convert_to_arn(
88
+ self, model_id: str, model_type: str = "foundation-model"
89
+ ) -> str:
90
+ """Convert a model ID to ARN format for the current region."""
91
+ if self._is_arn(model_id):
92
+ return model_id
93
+ return f"arn:aws:bedrock:{self.aws_region}::{model_type}/{model_id}"
94
+
95
+ def _suggest_arn_conversion(self, model_id: str, error_message: str) -> dict:
96
+ """Suggest ARN conversion when permission denied."""
97
+ if not self._is_arn(model_id):
98
+ foundation_arn = self._convert_to_arn(model_id, "foundation-model")
99
+ inference_arn = (
100
+ self._convert_to_arn(f"us.{model_id}", "inference-profile")
101
+ if not model_id.startswith("us.")
102
+ else self._convert_to_arn(model_id, "inference-profile")
103
+ )
104
+
105
+ return {
106
+ "success": False,
107
+ "message": error_message,
108
+ "suggestion": f"Try using ARN format to specify the exact region. Suggestions:\n"
109
+ f" Foundation model: {foundation_arn}\n"
110
+ f" Inference profile: {inference_arn}",
111
+ "suggested_arns": {
112
+ "foundation_model": foundation_arn,
113
+ "inference_profile": inference_arn,
114
+ },
115
+ }
116
+ return {"success": False, "message": error_message}
117
+
118
+ async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
119
+ """
120
+ Send a message to AWS Bedrock API.
121
+
122
+ Args:
123
+ message: User message
124
+ context: Conversation context
125
+
126
+ Returns:
127
+ ChatResponse with Bedrock's reply
128
+ """
129
+ try:
130
+ # Prepare request for Bedrock
131
+ request_body = self._prepare_bedrock_request(message, context)
132
+
133
+ # Build URL for the specific model
134
+ url = f"{self.base_url}/model/{self.model}/converse"
135
+
136
+ # Create headers with Bearer token authentication
137
+ headers = {
138
+ "Authorization": f"Bearer {self.api_key}",
139
+ "Content-Type": "application/json",
140
+ }
141
+
142
+ # Make API request
143
+ async with httpx.AsyncClient() as client:
144
+ response = await client.post(
145
+ url, headers=headers, content=request_body, timeout=30.0
146
+ )
147
+
148
+ # Handle response
149
+ return self._handle_response(response)
150
+
151
+ except httpx.TimeoutException:
152
+ raise NetworkError("Request to AWS Bedrock API timed out")
153
+ except httpx.RequestError as e:
154
+ raise NetworkError(f"Network error: {e}")
155
+ except Exception as e:
156
+ raise ProviderError(f"AWS Bedrock API error: {str(e)}")
157
+
158
+ async def send_message_with_tools(
159
+ self,
160
+ message: str,
161
+ context: ChatContext,
162
+ available_tools: List[ToolDefinition],
163
+ ) -> ChatResponse:
164
+ """
165
+ Send a message with available tools for Bedrock to use.
166
+
167
+ Args:
168
+ message: User message
169
+ context: Conversation context
170
+ available_tools: List of tools available for the model to use
171
+
172
+ Returns:
173
+ ChatResponse with Bedrock's reply and any tool calls
174
+ """
175
+ try:
176
+ # Prepare request for Bedrock with tools
177
+ request_body = self._prepare_bedrock_request_with_tools(
178
+ message, context, available_tools
179
+ )
180
+
181
+ # Build URL for the specific model - use appropriate endpoint based on model format
182
+ if self._is_arn(self.model):
183
+ # For ARN format, extract the actual model ID for the URL
184
+ model_for_url = self._extract_model_id_from_arn(self.model)
185
+ url = f"{self.base_url}/model/{model_for_url}/converse"
186
+ else:
187
+ url = f"{self.base_url}/model/{self.model}/converse"
188
+
189
+ # Create headers with Bearer token authentication
190
+ headers = {
191
+ "Authorization": f"Bearer {self.api_key}",
192
+ "Content-Type": "application/json",
193
+ }
194
+
195
+ # Make API request
196
+ async with httpx.AsyncClient() as client:
197
+ response = await client.post(
198
+ url, headers=headers, content=request_body, timeout=30.0
199
+ )
200
+
201
+ # Handle response with tool calls
202
+ return self._handle_response_with_tools(response)
203
+
204
+ except httpx.TimeoutException:
205
+ raise NetworkError("Request to AWS Bedrock API timed out")
206
+ except httpx.RequestError as e:
207
+ raise NetworkError(f"Network error: {e}")
208
+ except Exception as e:
209
+ raise ProviderError(f"AWS Bedrock API error: {str(e)}")
210
+
211
+ async def validate_credentials(self) -> bool:
212
+ """
213
+ Validate AWS Bedrock API key by checking API access (not model-specific).
214
+
215
+ Returns:
216
+ True if API key is valid (can access Bedrock API)
217
+ """
218
+ try:
219
+ # Test API key by listing foundation models (not model-specific)
220
+ url = f"https://bedrock.{self.aws_region}.amazonaws.com/foundation-models"
221
+ headers = {
222
+ "Authorization": f"Bearer {self.api_key}",
223
+ "Content-Type": "application/json",
224
+ }
225
+
226
+ async with httpx.AsyncClient() as client:
227
+ response = await client.get(url, headers=headers, timeout=10.0)
228
+
229
+ # API key is valid if we can access the foundation models endpoint
230
+ return response.status_code == 200
231
+
232
+ except Exception:
233
+ return False
234
+
235
+ async def validate_model_access(self) -> dict:
236
+ """
237
+ Test access to the specific model configured.
238
+
239
+ Returns:
240
+ Dict with 'success', 'message', and optional 'suggestion' keys
241
+ """
242
+ try:
243
+ # Test the specific model with a minimal request
244
+ test_request = {
245
+ "messages": [{"role": "user", "content": [{"text": "Hi"}]}],
246
+ "inferenceConfig": {"maxTokens": 10, "temperature": 0.1},
247
+ }
248
+
249
+ # If using ARN format, include the model ARN in the request
250
+ if self._is_arn(self.model):
251
+ test_request["modelId"] = self.model
252
+
253
+ test_body = json.dumps(test_request)
254
+
255
+ # Build URL for validation - use appropriate endpoint based on model format
256
+ if self._is_arn(self.model):
257
+ model_for_url = self._extract_model_id_from_arn(self.model)
258
+ url = f"{self.base_url}/model/{model_for_url}/converse"
259
+ else:
260
+ url = f"{self.base_url}/model/{self.model}/converse"
261
+
262
+ headers = {
263
+ "Authorization": f"Bearer {self.api_key}",
264
+ "Content-Type": "application/json",
265
+ }
266
+
267
+ async with httpx.AsyncClient() as client:
268
+ response = await client.post(
269
+ url, headers=headers, content=test_body, timeout=10.0
270
+ )
271
+
272
+ if response.status_code == 200:
273
+ return {
274
+ "success": True,
275
+ "message": f"Model {self.model} is accessible",
276
+ }
277
+ else:
278
+ try:
279
+ error_data = response.json()
280
+ error_msg = error_data.get(
281
+ "message", error_data.get("Message", "Unknown error")
282
+ )
283
+
284
+ # Parse different error types and provide intelligent suggestions
285
+ if "not authorized" in error_msg or "explicit deny" in error_msg:
286
+ return self._suggest_arn_conversion(
287
+ self.model,
288
+ f"Access denied to model {self.model}. This may be due to SCP restrictions or regional access issues.",
289
+ )
290
+ elif (
291
+ "not supported" in error_msg or "inference profile" in error_msg
292
+ ):
293
+ if not self._is_arn(self.model):
294
+ # Suggest ARN format for inference profile access
295
+ inference_arn = (
296
+ self._convert_to_arn(
297
+ f"us.{self.model}", "inference-profile"
298
+ )
299
+ if not self.model.startswith("us.")
300
+ else self._convert_to_arn(
301
+ self.model, "inference-profile"
302
+ )
303
+ )
304
+ return {
305
+ "success": False,
306
+ "message": f"Model {self.model} requires inference profile access",
307
+ "suggestion": f"Try using inference profile ARN: {inference_arn}",
308
+ }
309
+ else:
310
+ return {
311
+ "success": False,
312
+ "message": f"Model {self.model} requires inference profile access",
313
+ "suggestion": "Some models require using inference profiles instead of direct model IDs.",
314
+ }
315
+ elif "don't have access" in error_msg:
316
+ return self._suggest_arn_conversion(
317
+ self.model,
318
+ f"No access to model {self.model}. You may need to request access in the AWS Bedrock console.",
319
+ )
320
+ else:
321
+ return self._suggest_arn_conversion(
322
+ self.model, f"Model test failed: {error_msg}"
323
+ )
324
+ except:
325
+ return {
326
+ "success": False,
327
+ "message": f"Model test failed with HTTP {response.status_code}",
328
+ }
329
+
330
+ except Exception as e:
331
+ return {
332
+ "success": False,
333
+ "message": f"Failed to test model: {str(e)}",
334
+ }
335
+
336
+ def _prepare_bedrock_request(self, message: str, context: ChatContext) -> str:
337
+ """
338
+ Prepare request body for AWS Bedrock Converse API.
339
+
340
+ Args:
341
+ message: Current user message
342
+ context: Conversation context
343
+
344
+ Returns:
345
+ JSON string formatted for Bedrock Converse API
346
+ """
347
+ messages = []
348
+
349
+ # Add context messages
350
+ for msg in context.messages:
351
+ messages.append(
352
+ {"role": msg.role.value, "content": [{"text": msg.content}]}
353
+ )
354
+
355
+ # Add current message
356
+ messages.append({"role": "user", "content": [{"text": message}]})
357
+
358
+ request_data = {
359
+ "messages": messages,
360
+ "inferenceConfig": {
361
+ "maxTokens": self.max_tokens,
362
+ "temperature": self.temperature,
363
+ "topP": self.top_p,
364
+ },
365
+ }
366
+
367
+ # If using ARN format, include the model ARN in the request
368
+ if self._is_arn(self.model):
369
+ request_data["modelId"] = self.model
370
+
371
+ return json.dumps(request_data)
372
+
373
+ def _prepare_bedrock_request_with_tools(
374
+ self, message: str, context: ChatContext, tools: List[ToolDefinition]
375
+ ) -> str:
376
+ """
377
+ Prepare request body for AWS Bedrock Converse API with tools.
378
+
379
+ Args:
380
+ message: Current user message
381
+ context: Conversation context
382
+ tools: List of available tools
383
+
384
+ Returns:
385
+ JSON string formatted for Bedrock Converse API with tools
386
+ """
387
+ messages = []
388
+
389
+ # Add context messages
390
+ for msg in context.messages:
391
+ messages.append(
392
+ {"role": msg.role.value, "content": [{"text": msg.content}]}
393
+ )
394
+
395
+ # Add current message
396
+ messages.append({"role": "user", "content": [{"text": message}]})
397
+
398
+ request_data = {
399
+ "messages": messages,
400
+ "inferenceConfig": {
401
+ "maxTokens": self.max_tokens,
402
+ "temperature": self.temperature,
403
+ "topP": self.top_p,
404
+ },
405
+ }
406
+
407
+ # Add tools in Bedrock Converse format
408
+ if tools:
409
+ request_data["toolConfig"] = {
410
+ "tools": [self._convert_tool_to_bedrock_format(tool) for tool in tools]
411
+ }
412
+
413
+ return json.dumps(request_data)
414
+
415
+ def _convert_tool_to_bedrock_format(self, tool: ToolDefinition) -> Dict:
416
+ """
417
+ Convert tool definition to Bedrock format.
418
+
419
+ Args:
420
+ tool: Tool definition
421
+
422
+ Returns:
423
+ Tool formatted for Bedrock API
424
+ """
425
+ return {
426
+ "name": tool.name,
427
+ "description": tool.description,
428
+ "input_schema": tool.parameters,
429
+ }
430
+
431
+ def _handle_response(self, response: httpx.Response) -> ChatResponse:
432
+ """
433
+ Handle AWS Bedrock Converse API response.
434
+
435
+ Args:
436
+ response: HTTP response from Bedrock Converse API
437
+
438
+ Returns:
439
+ ChatResponse object
440
+
441
+ Raises:
442
+ Various provider errors based on response status
443
+ """
444
+ if response.status_code == 200:
445
+ data = response.json()
446
+
447
+ # Handle Bedrock Converse API response format
448
+ if "output" in data:
449
+ output = data.get("output", {})
450
+ message = output.get("message", {})
451
+ content_blocks = message.get("content", [])
452
+
453
+ if content_blocks and len(content_blocks) > 0:
454
+ text = content_blocks[0].get("text", "")
455
+ usage = data.get("usage", {})
456
+
457
+ return ChatResponse(
458
+ content=text,
459
+ model_used=self.model,
460
+ tokens_used=usage.get("outputTokens", 0),
461
+ timestamp=datetime.now(),
462
+ )
463
+ else:
464
+ raise ProviderError("Empty content in Bedrock response")
465
+ else:
466
+ raise ProviderError("Invalid response format from Bedrock Converse API")
467
+
468
+ elif response.status_code == 401:
469
+ raise AuthenticationError("Invalid API key for Bedrock")
470
+ elif response.status_code == 429:
471
+ raise RateLimitError("AWS Bedrock API rate limit exceeded")
472
+ elif response.status_code == 404:
473
+ raise ModelNotFoundError(f"Bedrock model '{self.model}' not found")
474
+ else:
475
+ try:
476
+ error_data = response.json()
477
+ error_msg = error_data.get(
478
+ "message", error_data.get("Message", "Unknown error")
479
+ )
480
+ # Include more error details for debugging
481
+ error_code = error_data.get("__type", error_data.get("code", ""))
482
+ if error_code:
483
+ error_msg = f"{error_code}: {error_msg}"
484
+ except:
485
+ error_msg = f"HTTP {response.status_code}: {response.text[:200] if response.text else 'No response body'}"
486
+
487
+ raise ProviderError(f"AWS Bedrock API error: {error_msg}")
488
+
489
+ def _handle_response_with_tools(self, response: httpx.Response) -> ChatResponse:
490
+ """
491
+ Handle AWS Bedrock Converse API response with tool calls.
492
+
493
+ Args:
494
+ response: HTTP response from Bedrock Converse API
495
+
496
+ Returns:
497
+ ChatResponse object with tool calls
498
+ """
499
+ if response.status_code == 200:
500
+ data = response.json()
501
+
502
+ if "output" in data:
503
+ output = data.get("output", {})
504
+ message = output.get("message", {})
505
+ content_blocks = message.get("content", [])
506
+ text_content = ""
507
+ tool_calls = []
508
+
509
+ for block in content_blocks:
510
+ if block.get("type") == "text":
511
+ text_content += block.get("text", "")
512
+ elif block.get("type") == "toolUse":
513
+ tool_calls.append(
514
+ ToolCall(
515
+ name=block.get("name", ""),
516
+ arguments=block.get("input", {}),
517
+ )
518
+ )
519
+
520
+ usage = data.get("usage", {})
521
+
522
+ return ChatResponse(
523
+ content=text_content,
524
+ model_used=self.model,
525
+ tokens_used=usage.get("outputTokens", 0),
526
+ tool_calls=tool_calls if tool_calls else None,
527
+ timestamp=datetime.now(),
528
+ )
529
+ else:
530
+ raise ProviderError("Invalid response format from Bedrock Converse API")
531
+ else:
532
+ return self._handle_response(response)
533
+
534
+ def get_model_info(self) -> EnhancedModelInfo:
535
+ """
536
+ Get information about the current Bedrock model.
537
+ """
538
+ model_configs = {
539
+ "anthropic.claude-3-sonnet-20240229-v1:0": {
540
+ "description": "Claude 3 Sonnet on AWS Bedrock - Balanced performance",
541
+ "max_tokens": 200000,
542
+ "cost_per_million_input": 3.0,
543
+ "cost_per_million_output": 15.0,
544
+ "swe_score": 73.0,
545
+ "supports_tools": True,
546
+ "supports_multimodal": True,
547
+ },
548
+ "anthropic.claude-3-haiku-20240307-v1:0": {
549
+ "description": "Claude 3 Haiku on AWS Bedrock - Fast and efficient",
550
+ "max_tokens": 200000,
551
+ "cost_per_million_input": 0.25,
552
+ "cost_per_million_output": 1.25,
553
+ "swe_score": 75.9,
554
+ "supports_tools": True,
555
+ "supports_multimodal": True,
556
+ },
557
+ "anthropic.claude-3-opus-20240229-v1:0": {
558
+ "description": "Claude 3 Opus on AWS Bedrock - Most capable",
559
+ "max_tokens": 200000,
560
+ "cost_per_million_input": 15.0,
561
+ "cost_per_million_output": 75.0,
562
+ "swe_score": 84.9,
563
+ "supports_tools": True,
564
+ "supports_multimodal": True,
565
+ },
566
+ "anthropic.claude-3-5-sonnet-20241022-v2:0": {
567
+ "description": "Claude 3.5 Sonnet on AWS Bedrock - Latest and most capable",
568
+ "max_tokens": 200000,
569
+ "cost_per_million_input": 3.0,
570
+ "cost_per_million_output": 15.0,
571
+ "swe_score": 88.7,
572
+ "supports_tools": True,
573
+ "supports_multimodal": True,
574
+ },
575
+ }
576
+
577
+ config = model_configs.get(
578
+ self.model,
579
+ {
580
+ "description": f"AWS Bedrock model {self.model}",
581
+ "max_tokens": 200000,
582
+ "cost_per_million_input": 3.0,
583
+ "cost_per_million_output": 15.0,
584
+ "swe_score": 70.0,
585
+ "supports_tools": True,
586
+ "supports_multimodal": True,
587
+ },
588
+ )
589
+
590
+ enhanced_info = EnhancedModelInfo(
591
+ name=self.model,
592
+ provider="bedrock",
593
+ description=config["description"],
594
+ max_tokens=config["max_tokens"],
595
+ cost_per_million_input=config["cost_per_million_input"],
596
+ cost_per_million_output=config["cost_per_million_output"],
597
+ swe_score=config["swe_score"],
598
+ available=True,
599
+ supports_tools=config["supports_tools"],
600
+ supports_multimodal=config["supports_multimodal"],
601
+ latest_version="claude-3-5-sonnet" in self.model,
602
+ context_window=config["max_tokens"],
603
+ is_free=False,
604
+ release_date=datetime(2024, 6, 1), # Approximate Bedrock availability
605
+ )
606
+
607
+ # Update SWE rating based on score
608
+ enhanced_info.update_swe_rating()
609
+
610
+ return enhanced_info
611
+
612
+ def get_available_models(self) -> List[EnhancedModelInfo]:
613
+ """
614
+ Get list of available AWS Bedrock models.
615
+ """
616
+ models = [
617
+ EnhancedModelInfo(
618
+ name="anthropic.claude-3-5-sonnet-20241022-v2:0",
619
+ provider="bedrock",
620
+ description="Claude 3.5 Sonnet on AWS Bedrock - Latest and most capable",
621
+ max_tokens=200000,
622
+ cost_per_million_input=3.0,
623
+ cost_per_million_output=15.0,
624
+ swe_score=88.7,
625
+ available=True,
626
+ supports_tools=True,
627
+ supports_multimodal=True,
628
+ latest_version=True,
629
+ context_window=200000,
630
+ is_free=False,
631
+ release_date=datetime(2024, 10, 1),
632
+ ),
633
+ EnhancedModelInfo(
634
+ name="anthropic.claude-3-opus-20240229-v1:0",
635
+ provider="bedrock",
636
+ description="Claude 3 Opus on AWS Bedrock - Most capable",
637
+ max_tokens=200000,
638
+ cost_per_million_input=15.0,
639
+ cost_per_million_output=75.0,
640
+ swe_score=84.9,
641
+ available=True,
642
+ supports_tools=True,
643
+ supports_multimodal=True,
644
+ context_window=200000,
645
+ is_free=False,
646
+ release_date=datetime(2024, 6, 1),
647
+ ),
648
+ EnhancedModelInfo(
649
+ name="anthropic.claude-3-sonnet-20240229-v1:0",
650
+ provider="bedrock",
651
+ description="Claude 3 Sonnet on AWS Bedrock - Balanced performance",
652
+ max_tokens=200000,
653
+ cost_per_million_input=3.0,
654
+ cost_per_million_output=15.0,
655
+ swe_score=73.0,
656
+ available=True,
657
+ supports_tools=True,
658
+ supports_multimodal=True,
659
+ context_window=200000,
660
+ is_free=False,
661
+ release_date=datetime(2024, 6, 1),
662
+ ),
663
+ EnhancedModelInfo(
664
+ name="anthropic.claude-3-haiku-20240307-v1:0",
665
+ provider="bedrock",
666
+ description="Claude 3 Haiku on AWS Bedrock - Fast and efficient",
667
+ max_tokens=200000,
668
+ cost_per_million_input=0.25,
669
+ cost_per_million_output=1.25,
670
+ swe_score=75.9,
671
+ available=True,
672
+ supports_tools=True,
673
+ supports_multimodal=True,
674
+ context_window=200000,
675
+ is_free=False,
676
+ release_date=datetime(2024, 6, 1),
677
+ ),
678
+ ]
679
+
680
+ # Update SWE ratings for all models
681
+ for model in models:
682
+ model.update_swe_rating()
683
+
684
+ return models
685
+
686
+ def supports_tools(self) -> bool:
687
+ """
688
+ Check if Bedrock provider supports tool calling.
689
+
690
+ Returns:
691
+ True - Claude models on Bedrock support tool calling
692
+ """
693
+ return True
694
+
695
+ def supports_multimodal(self) -> bool:
696
+ """
697
+ Check if Bedrock provider supports multimodal inputs.
698
+
699
+ Returns:
700
+ True - Claude 3 models support multimodal inputs
701
+ """
702
+ return True
703
+
704
+ def supports_streaming(self) -> bool:
705
+ """
706
+ Check if Bedrock provider supports streaming responses.
707
+
708
+ Returns:
709
+ True - Bedrock supports streaming
710
+ """
711
+ return True