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,476 @@
1
+ """
2
+ Perplexity AI provider implementation for Omnimancer.
3
+
4
+ This module provides the Perplexity AI provider implementation using Perplexity's API
5
+ with support for search-enabled conversations and recency filtering.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Dict, List
10
+
11
+ import httpx
12
+
13
+ from ..core.models import ChatContext, ChatResponse, EnhancedModelInfo
14
+ from ..utils.errors import (
15
+ AuthenticationError,
16
+ ModelNotFoundError,
17
+ NetworkError,
18
+ ProviderError,
19
+ RateLimitError,
20
+ )
21
+ from .base import BaseProvider
22
+
23
+
24
+ class PerplexityProvider(BaseProvider):
25
+ """
26
+ Perplexity AI provider implementation using Perplexity's API.
27
+
28
+ Supports search-enabled conversations with real-time web information
29
+ and recency filtering for up-to-date responses.
30
+ """
31
+
32
+ BASE_URL = "https://api.perplexity.ai"
33
+
34
+ def __init__(self, api_key: str, model: str = "", **kwargs):
35
+ """
36
+ Initialize Perplexity provider.
37
+
38
+ Args:
39
+ api_key: Perplexity API key
40
+ model: Perplexity model to use (e.g., 'llama-3.1-sonar-small-128k-online', 'llama-3.1-sonar-large-128k-online')
41
+ **kwargs: Additional configuration including search settings
42
+ """
43
+ super().__init__(
44
+ api_key, model or "llama-3.1-sonar-small-128k-online", **kwargs
45
+ )
46
+ self.max_tokens = kwargs.get("max_tokens", 4096)
47
+ self.temperature = kwargs.get("temperature", 0.2)
48
+ self.search_enabled = kwargs.get("search_enabled", True)
49
+ self.search_recency_filter = kwargs.get(
50
+ "search_recency_filter", "month"
51
+ ) # hour, day, week, month, year
52
+ self.return_citations = kwargs.get("return_citations", False)
53
+ self.return_images = kwargs.get("return_images", False)
54
+ self.return_related_questions = kwargs.get("return_related_questions", False)
55
+
56
+ async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
57
+ """
58
+ Send a message to Perplexity API with search capabilities.
59
+
60
+ Args:
61
+ message: User message
62
+ context: Conversation context
63
+
64
+ Returns:
65
+ ChatResponse with Perplexity's reply including search results
66
+ """
67
+ try:
68
+ # Prepare messages for Perplexity API
69
+ messages = self._prepare_messages(message, context)
70
+
71
+ # Build request payload
72
+ payload = {
73
+ "model": self.model,
74
+ "messages": messages,
75
+ "max_tokens": self.max_tokens,
76
+ "temperature": self.temperature,
77
+ "return_citations": self.return_citations,
78
+ "return_images": self.return_images,
79
+ "return_related_questions": self.return_related_questions,
80
+ }
81
+
82
+ # Add search-specific parameters for online models
83
+ if self._is_online_model():
84
+ payload["search_recency_filter"] = self.search_recency_filter
85
+
86
+ # Make API request
87
+ async with httpx.AsyncClient() as client:
88
+ response = await client.post(
89
+ f"{self.BASE_URL}/chat/completions",
90
+ headers={
91
+ "Content-Type": "application/json",
92
+ "Authorization": f"Bearer {self.api_key}",
93
+ },
94
+ json=payload,
95
+ timeout=60.0, # Longer timeout for search operations
96
+ )
97
+
98
+ # Handle response
99
+ return self._handle_response(response)
100
+
101
+ except httpx.TimeoutException:
102
+ raise NetworkError("Request to Perplexity API timed out")
103
+ except httpx.RequestError as e:
104
+ raise NetworkError(f"Network error: {e}")
105
+ except (
106
+ AuthenticationError,
107
+ RateLimitError,
108
+ ModelNotFoundError,
109
+ ProviderError,
110
+ ):
111
+ # Re-raise provider-specific errors without wrapping
112
+ raise
113
+ except Exception as e:
114
+ raise ProviderError(f"Unexpected error: {e}")
115
+
116
+ async def validate_credentials(self) -> bool:
117
+ """
118
+ Validate Perplexity API credentials by making a test request.
119
+
120
+ Returns:
121
+ True if credentials are valid
122
+ """
123
+ try:
124
+ async with httpx.AsyncClient() as client:
125
+ response = await client.post(
126
+ f"{self.BASE_URL}/chat/completions",
127
+ headers={
128
+ "Content-Type": "application/json",
129
+ "Authorization": f"Bearer {self.api_key}",
130
+ },
131
+ json={
132
+ "model": self.model,
133
+ "messages": [{"role": "user", "content": "Hi"}],
134
+ "max_tokens": 10,
135
+ },
136
+ timeout=10.0,
137
+ )
138
+
139
+ return response.status_code == 200
140
+
141
+ except Exception:
142
+ return False
143
+
144
+ def _prepare_messages(
145
+ self, message: str, context: ChatContext
146
+ ) -> List[Dict[str, str]]:
147
+ """
148
+ Prepare messages for Perplexity API format.
149
+
150
+ Args:
151
+ message: Current user message
152
+ context: Conversation context
153
+
154
+ Returns:
155
+ List of messages formatted for Perplexity API
156
+ """
157
+ messages = []
158
+
159
+ # Add context messages
160
+ for msg in context.messages:
161
+ messages.append({"role": msg.role.value, "content": msg.content})
162
+
163
+ # Add current message
164
+ messages.append({"role": "user", "content": message})
165
+
166
+ return messages
167
+
168
+ def _handle_response(self, response: httpx.Response) -> ChatResponse:
169
+ """
170
+ Handle Perplexity API response.
171
+
172
+ Args:
173
+ response: HTTP response from Perplexity API
174
+
175
+ Returns:
176
+ ChatResponse object
177
+
178
+ Raises:
179
+ Various provider errors based on response status
180
+ """
181
+ if response.status_code == 200:
182
+ data = response.json()
183
+ choices = data.get("choices", [])
184
+
185
+ if choices and len(choices) > 0:
186
+ message = choices[0].get("message", {})
187
+ content = message.get("content", "")
188
+ usage = data.get("usage", {})
189
+
190
+ # Extract citations and additional metadata
191
+ citations = data.get("citations", [])
192
+ images = data.get("images", [])
193
+ related_questions = data.get("related_questions", [])
194
+
195
+ # Format content with citations if available
196
+ formatted_content = self._format_content_with_metadata(
197
+ content, citations, images, related_questions
198
+ )
199
+
200
+ return ChatResponse(
201
+ content=formatted_content,
202
+ model_used=self.model,
203
+ tokens_used=usage.get("total_tokens", 0),
204
+ timestamp=datetime.now(),
205
+ )
206
+ else:
207
+ raise ProviderError("Empty response from Perplexity API")
208
+
209
+ elif response.status_code == 401:
210
+ raise AuthenticationError("Invalid Perplexity API key")
211
+ elif response.status_code == 429:
212
+ raise RateLimitError("Perplexity API rate limit exceeded")
213
+ elif response.status_code == 404:
214
+ raise ModelNotFoundError(f"Perplexity model '{self.model}' not found")
215
+ else:
216
+ try:
217
+ error_data = response.json()
218
+ error_msg = error_data.get("error", {}).get("message", "Unknown error")
219
+ except:
220
+ error_msg = f"HTTP {response.status_code}"
221
+
222
+ raise ProviderError(f"Perplexity API error: {error_msg}")
223
+
224
+ def _format_content_with_metadata(
225
+ self,
226
+ content: str,
227
+ citations: List[Dict],
228
+ images: List[Dict],
229
+ related_questions: List[str],
230
+ ) -> str:
231
+ """
232
+ Format response content with citations and metadata.
233
+
234
+ Args:
235
+ content: Main response content
236
+ citations: List of citation objects
237
+ images: List of image objects
238
+ related_questions: List of related questions
239
+
240
+ Returns:
241
+ Formatted content string
242
+ """
243
+ formatted = content
244
+
245
+ # Add citations if available
246
+ if citations and self.return_citations:
247
+ formatted += "\n\n**Sources:**\n"
248
+ for i, citation in enumerate(citations, 1):
249
+ title = citation.get("title", "Unknown")
250
+ url = citation.get("url", "")
251
+ formatted += f"{i}. [{title}]({url})\n"
252
+
253
+ # Add related questions if available
254
+ if related_questions and self.return_related_questions:
255
+ formatted += "\n\n**Related Questions:**\n"
256
+ for question in related_questions:
257
+ formatted += f"• {question}\n"
258
+
259
+ return formatted
260
+
261
+ def _is_online_model(self) -> bool:
262
+ """
263
+ Check if the current model is an online/search-enabled model.
264
+
265
+ Returns:
266
+ True if model supports online search
267
+ """
268
+ online_models = [
269
+ "llama-3.1-sonar-small-128k-online",
270
+ "llama-3.1-sonar-large-128k-online",
271
+ "llama-3.1-sonar-huge-128k-online",
272
+ ]
273
+ return self.model in online_models
274
+
275
+ def get_model_info(self) -> EnhancedModelInfo:
276
+ """
277
+ Get information about the current Perplexity model.
278
+ """
279
+ model_configs = {
280
+ "llama-3.1-sonar-small-128k-online": {
281
+ "description": "Llama 3.1 Sonar Small with real-time web search",
282
+ "max_tokens": 127072,
283
+ "cost_per_million_input": 0.2,
284
+ "cost_per_million_output": 0.2,
285
+ "swe_score": 45.2,
286
+ "supports_tools": True,
287
+ "supports_multimodal": False,
288
+ },
289
+ "llama-3.1-sonar-large-128k-online": {
290
+ "description": "Llama 3.1 Sonar Large with real-time web search",
291
+ "max_tokens": 127072,
292
+ "cost_per_million_input": 1.0,
293
+ "cost_per_million_output": 1.0,
294
+ "swe_score": 52.4,
295
+ "supports_tools": True,
296
+ "supports_multimodal": False,
297
+ },
298
+ "llama-3.1-sonar-huge-128k-online": {
299
+ "description": "Llama 3.1 Sonar Huge with real-time web search",
300
+ "max_tokens": 127072,
301
+ "cost_per_million_input": 5.0,
302
+ "cost_per_million_output": 5.0,
303
+ "swe_score": 58.1,
304
+ "supports_tools": True,
305
+ "supports_multimodal": False,
306
+ },
307
+ "llama-3.1-sonar-small-128k-chat": {
308
+ "description": "Llama 3.1 Sonar Small for chat without search",
309
+ "max_tokens": 127072,
310
+ "cost_per_million_input": 0.2,
311
+ "cost_per_million_output": 0.2,
312
+ "swe_score": 45.2,
313
+ "supports_tools": False,
314
+ "supports_multimodal": False,
315
+ },
316
+ "llama-3.1-sonar-large-128k-chat": {
317
+ "description": "Llama 3.1 Sonar Large for chat without search",
318
+ "max_tokens": 127072,
319
+ "cost_per_million_input": 1.0,
320
+ "cost_per_million_output": 1.0,
321
+ "swe_score": 52.4,
322
+ "supports_tools": False,
323
+ "supports_multimodal": False,
324
+ },
325
+ }
326
+
327
+ config = model_configs.get(
328
+ self.model,
329
+ {
330
+ "description": f"Perplexity model {self.model}",
331
+ "max_tokens": 127072,
332
+ "cost_per_million_input": 1.0,
333
+ "cost_per_million_output": 1.0,
334
+ "swe_score": 50.0,
335
+ "supports_tools": False,
336
+ "supports_multimodal": False,
337
+ },
338
+ )
339
+
340
+ enhanced_info = EnhancedModelInfo(
341
+ name=self.model,
342
+ provider="perplexity",
343
+ description=config["description"],
344
+ max_tokens=config["max_tokens"],
345
+ cost_per_million_input=config["cost_per_million_input"],
346
+ cost_per_million_output=config["cost_per_million_output"],
347
+ swe_score=config["swe_score"],
348
+ available=True,
349
+ supports_tools=config["supports_tools"],
350
+ supports_multimodal=config["supports_multimodal"],
351
+ latest_version=self.model == "llama-3.1-sonar-huge-128k-online",
352
+ context_window=config["max_tokens"],
353
+ is_free=False,
354
+ release_date=datetime(2024, 7, 1), # Approximate release date
355
+ )
356
+
357
+ # Update SWE rating based on score
358
+ enhanced_info.update_swe_rating()
359
+
360
+ return enhanced_info
361
+
362
+ def get_available_models(self) -> List[EnhancedModelInfo]:
363
+ """
364
+ Get list of available Perplexity models.
365
+ """
366
+ models = [
367
+ EnhancedModelInfo(
368
+ name="llama-3.1-sonar-small-128k-online",
369
+ provider="perplexity",
370
+ description="Llama 3.1 Sonar Small with real-time web search",
371
+ max_tokens=127072,
372
+ cost_per_million_input=0.2,
373
+ cost_per_million_output=0.2,
374
+ swe_score=45.2,
375
+ available=True,
376
+ supports_tools=True,
377
+ supports_multimodal=False,
378
+ context_window=127072,
379
+ is_free=False,
380
+ release_date=datetime(2024, 7, 1),
381
+ ),
382
+ EnhancedModelInfo(
383
+ name="llama-3.1-sonar-large-128k-online",
384
+ provider="perplexity",
385
+ description="Llama 3.1 Sonar Large with real-time web search",
386
+ max_tokens=127072,
387
+ cost_per_million_input=1.0,
388
+ cost_per_million_output=1.0,
389
+ swe_score=52.4,
390
+ available=True,
391
+ supports_tools=True,
392
+ supports_multimodal=False,
393
+ context_window=127072,
394
+ is_free=False,
395
+ release_date=datetime(2024, 7, 1),
396
+ ),
397
+ EnhancedModelInfo(
398
+ name="llama-3.1-sonar-huge-128k-online",
399
+ provider="perplexity",
400
+ description="Llama 3.1 Sonar Huge with real-time web search",
401
+ max_tokens=127072,
402
+ cost_per_million_input=5.0,
403
+ cost_per_million_output=5.0,
404
+ swe_score=58.1,
405
+ available=True,
406
+ supports_tools=True,
407
+ supports_multimodal=False,
408
+ latest_version=True,
409
+ context_window=127072,
410
+ is_free=False,
411
+ release_date=datetime(2024, 7, 1),
412
+ ),
413
+ EnhancedModelInfo(
414
+ name="llama-3.1-sonar-small-128k-chat",
415
+ provider="perplexity",
416
+ description="Llama 3.1 Sonar Small for chat without search",
417
+ max_tokens=127072,
418
+ cost_per_million_input=0.2,
419
+ cost_per_million_output=0.2,
420
+ swe_score=45.2,
421
+ available=True,
422
+ supports_tools=False,
423
+ supports_multimodal=False,
424
+ context_window=127072,
425
+ is_free=False,
426
+ release_date=datetime(2024, 7, 1),
427
+ ),
428
+ EnhancedModelInfo(
429
+ name="llama-3.1-sonar-large-128k-chat",
430
+ provider="perplexity",
431
+ description="Llama 3.1 Sonar Large for chat without search",
432
+ max_tokens=127072,
433
+ cost_per_million_input=1.0,
434
+ cost_per_million_output=1.0,
435
+ swe_score=52.4,
436
+ available=True,
437
+ supports_tools=False,
438
+ supports_multimodal=False,
439
+ context_window=127072,
440
+ is_free=False,
441
+ release_date=datetime(2024, 7, 1),
442
+ ),
443
+ ]
444
+
445
+ # Update SWE ratings for all models
446
+ for model in models:
447
+ model.update_swe_rating()
448
+
449
+ return models
450
+
451
+ def supports_tools(self) -> bool:
452
+ """
453
+ Check if Perplexity provider supports tool calling.
454
+
455
+ Returns:
456
+ True for online models that support search tools
457
+ """
458
+ return self._is_online_model()
459
+
460
+ def supports_multimodal(self) -> bool:
461
+ """
462
+ Check if Perplexity provider supports multimodal inputs.
463
+
464
+ Returns:
465
+ False - Current Perplexity models don't support multimodal inputs
466
+ """
467
+ return False
468
+
469
+ def supports_streaming(self) -> bool:
470
+ """
471
+ Check if Perplexity provider supports streaming responses.
472
+
473
+ Returns:
474
+ True - Perplexity supports streaming
475
+ """
476
+ return True