daita-agents 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.

Potentially problematic release.


This version of daita-agents might be problematic. Click here for more details.

Files changed (69) hide show
  1. daita/__init__.py +208 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +722 -0
  4. daita/agents/substrate.py +895 -0
  5. daita/cli/__init__.py +145 -0
  6. daita/cli/__main__.py +7 -0
  7. daita/cli/ascii_art.py +44 -0
  8. daita/cli/core/__init__.py +0 -0
  9. daita/cli/core/create.py +254 -0
  10. daita/cli/core/deploy.py +473 -0
  11. daita/cli/core/deployments.py +309 -0
  12. daita/cli/core/import_detector.py +219 -0
  13. daita/cli/core/init.py +382 -0
  14. daita/cli/core/logs.py +239 -0
  15. daita/cli/core/managed_deploy.py +709 -0
  16. daita/cli/core/run.py +648 -0
  17. daita/cli/core/status.py +421 -0
  18. daita/cli/core/test.py +239 -0
  19. daita/cli/core/webhooks.py +172 -0
  20. daita/cli/main.py +588 -0
  21. daita/cli/utils.py +541 -0
  22. daita/config/__init__.py +62 -0
  23. daita/config/base.py +159 -0
  24. daita/config/settings.py +184 -0
  25. daita/core/__init__.py +262 -0
  26. daita/core/decision_tracing.py +701 -0
  27. daita/core/exceptions.py +480 -0
  28. daita/core/focus.py +251 -0
  29. daita/core/interfaces.py +76 -0
  30. daita/core/plugin_tracing.py +550 -0
  31. daita/core/relay.py +695 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +444 -0
  34. daita/core/tools.py +402 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1084 -0
  37. daita/display/__init__.py +1 -0
  38. daita/display/console.py +160 -0
  39. daita/execution/__init__.py +58 -0
  40. daita/execution/client.py +856 -0
  41. daita/execution/exceptions.py +92 -0
  42. daita/execution/models.py +317 -0
  43. daita/llm/__init__.py +60 -0
  44. daita/llm/anthropic.py +166 -0
  45. daita/llm/base.py +373 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +152 -0
  48. daita/llm/grok.py +114 -0
  49. daita/llm/mock.py +135 -0
  50. daita/llm/openai.py +109 -0
  51. daita/plugins/__init__.py +141 -0
  52. daita/plugins/base.py +37 -0
  53. daita/plugins/base_db.py +167 -0
  54. daita/plugins/elasticsearch.py +844 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +510 -0
  57. daita/plugins/mysql.py +351 -0
  58. daita/plugins/postgresql.py +331 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +529 -0
  61. daita/plugins/s3.py +761 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.1.0.dist-info/METADATA +350 -0
  65. daita_agents-0.1.0.dist-info/RECORD +69 -0
  66. daita_agents-0.1.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
  69. daita_agents-0.1.0.dist-info/top_level.txt +1 -0
daita/cli/utils.py ADDED
@@ -0,0 +1,541 @@
1
+ """
2
+ Shared utilities for Daita CLI.
3
+
4
+ Common functions used across CLI core modules to avoid duplication.
5
+ """
6
+ import os
7
+ import json
8
+ import yaml
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Dict, Any, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # ======= Project Management =======
16
+
17
+ def find_project_root(start_path: Optional[Path] = None) -> Optional[Path]:
18
+ """
19
+ Find the root directory of a Daita project.
20
+
21
+ Searches upward from the given path (or current directory) looking for .daita folder.
22
+
23
+ Args:
24
+ start_path: Path to start searching from (defaults to current directory)
25
+
26
+ Returns:
27
+ Path to project root, or None if not in a Daita project
28
+ """
29
+ current = start_path or Path.cwd()
30
+
31
+ for path in [current] + list(current.parents):
32
+ if (path / '.daita').exists():
33
+ return path
34
+
35
+ return None
36
+
37
+ def ensure_project_root() -> Path:
38
+ """
39
+ Find project root and raise error if not in a project.
40
+
41
+ Returns:
42
+ Path to project root
43
+
44
+ Raises:
45
+ ValueError: If not in a Daita project
46
+ """
47
+ project_root = find_project_root()
48
+ if not project_root:
49
+ raise ValueError("Not in a Daita project. Run 'daita init' first.")
50
+ return project_root
51
+
52
+ def load_project_config(project_root: Path) -> Optional[Dict[str, Any]]:
53
+ """
54
+ Load project configuration from daita-project.yaml.
55
+
56
+ Args:
57
+ project_root: Path to project root directory
58
+
59
+ Returns:
60
+ Project configuration dictionary, or None if file doesn't exist
61
+ """
62
+ config_file = project_root / 'daita-project.yaml'
63
+
64
+ if not config_file.exists():
65
+ return None
66
+
67
+ try:
68
+ with open(config_file, 'r') as f:
69
+ return yaml.safe_load(f)
70
+ except Exception as e:
71
+ logger.warning(f"Failed to load project config: {str(e)}")
72
+ return None
73
+
74
+ def save_project_config(project_root: Path, config: Dict[str, Any]) -> None:
75
+ """
76
+ Save project configuration to daita-project.yaml.
77
+
78
+ Args:
79
+ project_root: Path to project root directory
80
+ config: Configuration dictionary to save
81
+ """
82
+ config_file = project_root / 'daita-project.yaml'
83
+
84
+ try:
85
+ with open(config_file, 'w') as f:
86
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
87
+ except Exception as e:
88
+ raise ValueError(f"Failed to save project config: {str(e)}")
89
+
90
+ # ======= Deployment Management =======
91
+
92
+ def load_deployments(project_root: Path) -> list:
93
+ """
94
+ Load deployment history from .daita/deployments.json.
95
+
96
+ Args:
97
+ project_root: Path to project root directory
98
+
99
+ Returns:
100
+ List of deployment records
101
+ """
102
+ deployments_file = project_root / '.daita' / 'deployments.json'
103
+
104
+ if not deployments_file.exists():
105
+ return []
106
+
107
+ try:
108
+ with open(deployments_file, 'r') as f:
109
+ return json.load(f)
110
+ except Exception as e:
111
+ logger.warning(f"Failed to load deployments: {str(e)}")
112
+ return []
113
+
114
+ def save_deployments(project_root: Path, deployments: list) -> None:
115
+ """
116
+ Save deployment history to .daita/deployments.json.
117
+
118
+ Args:
119
+ project_root: Path to project root directory
120
+ deployments: List of deployment records to save
121
+ """
122
+ deployments_file = project_root / '.daita' / 'deployments.json'
123
+
124
+ # Ensure .daita directory exists
125
+ deployments_file.parent.mkdir(exist_ok=True)
126
+
127
+ try:
128
+ with open(deployments_file, 'w') as f:
129
+ json.dump(deployments, f, indent=2)
130
+ except Exception as e:
131
+ raise ValueError(f"Failed to save deployments: {str(e)}")
132
+
133
+ # ======= File Operations =======
134
+
135
+ def copy_files(src_dir: Path, dest_dir: Path, pattern: str, verbose: bool = False) -> None:
136
+ """
137
+ Copy files matching a pattern from source to destination.
138
+
139
+ Args:
140
+ src_dir: Source directory
141
+ dest_dir: Destination directory
142
+ pattern: File pattern to match (e.g., 'agents/', '*.py')
143
+ verbose: Whether to print detailed output
144
+ """
145
+ src_path = src_dir / pattern
146
+
147
+ if src_path.is_file():
148
+ # Single file
149
+ dest_file = dest_dir / pattern
150
+ dest_file.parent.mkdir(parents=True, exist_ok=True)
151
+ dest_file.write_bytes(src_path.read_bytes())
152
+ if verbose:
153
+ print(f" Copied: {pattern}")
154
+
155
+ elif src_path.is_dir():
156
+ # Directory - copy all Python files
157
+ for file_path in src_path.rglob('*.py'):
158
+ rel_path = file_path.relative_to(src_dir)
159
+ dest_file = dest_dir / rel_path
160
+ dest_file.parent.mkdir(parents=True, exist_ok=True)
161
+ dest_file.write_bytes(file_path.read_bytes())
162
+ if verbose:
163
+ print(f" Copied: {rel_path}")
164
+
165
+ def list_python_files(directory: Path) -> list:
166
+ """
167
+ List Python files in a directory (excluding __init__.py).
168
+
169
+ Args:
170
+ directory: Directory to search
171
+
172
+ Returns:
173
+ List of Python file stems (names without .py extension)
174
+ """
175
+ if not directory.exists():
176
+ return []
177
+
178
+ files = []
179
+ for file in directory.glob('*.py'):
180
+ if file.name != '__init__.py':
181
+ files.append(file.stem)
182
+
183
+ return sorted(files)
184
+
185
+ # ======= String Utilities =======
186
+
187
+ def to_class_name(snake_case_name: str) -> str:
188
+ """
189
+ Convert snake_case to PascalCase for class names.
190
+
191
+ Args:
192
+ snake_case_name: Snake case string (e.g., 'my_agent')
193
+
194
+ Returns:
195
+ PascalCase string (e.g., 'MyAgent')
196
+ """
197
+ return ''.join(word.capitalize() for word in snake_case_name.split('_'))
198
+
199
+ def to_snake_case(name: str) -> str:
200
+ """
201
+ Convert a name to snake_case.
202
+
203
+ Args:
204
+ name: Input name (any case)
205
+
206
+ Returns:
207
+ Snake case string
208
+ """
209
+ # Replace spaces and hyphens with underscores
210
+ name = name.replace(' ', '_').replace('-', '_')
211
+
212
+ # Convert to lowercase
213
+ name = name.lower()
214
+
215
+ # Remove any non-alphanumeric characters except underscores
216
+ import re
217
+ name = re.sub(r'[^a-z0-9_]', '', name)
218
+
219
+ # Remove duplicate underscores
220
+ while '__' in name:
221
+ name = name.replace('__', '_')
222
+
223
+ # Remove leading/trailing underscores
224
+ return name.strip('_')
225
+
226
+ def format_result_preview(result: Any, max_length: int = 100) -> str:
227
+ """
228
+ Format a result for preview display.
229
+
230
+ Args:
231
+ result: Result to format
232
+ max_length: Maximum length of preview string
233
+
234
+ Returns:
235
+ Formatted preview string
236
+ """
237
+ if isinstance(result, dict):
238
+ # Show status and key count
239
+ status = result.get('status', 'unknown')
240
+ key_count = len(result.keys())
241
+ return f"status={status}, {key_count} keys"
242
+
243
+ elif isinstance(result, list):
244
+ # Show list length and type of first item
245
+ if result:
246
+ first_type = type(result[0]).__name__
247
+ return f"list[{len(result)}] of {first_type}"
248
+ else:
249
+ return "empty list"
250
+
251
+ else:
252
+ # Show truncated string representation
253
+ result_str = str(result)
254
+ if len(result_str) > max_length:
255
+ return result_str[:max_length] + "..."
256
+ return result_str
257
+
258
+ # ======= Environment Utilities =======
259
+
260
+ def has_api_key() -> bool:
261
+ """
262
+ Check if any API key is configured in environment.
263
+
264
+ Returns:
265
+ True if at least one API key is found
266
+ """
267
+ api_keys = [
268
+ 'OPENAI_API_KEY',
269
+ 'ANTHROPIC_API_KEY',
270
+ 'GOOGLE_API_KEY',
271
+ 'GEMINI_API_KEY',
272
+ 'XAI_API_KEY',
273
+ 'GROK_API_KEY'
274
+ ]
275
+
276
+ return any(os.getenv(key) for key in api_keys)
277
+
278
+ def get_configured_providers() -> list:
279
+ """
280
+ Get list of LLM providers that have API keys configured.
281
+
282
+ Returns:
283
+ List of provider names
284
+ """
285
+ providers = []
286
+
287
+ if os.getenv('OPENAI_API_KEY'):
288
+ providers.append('openai')
289
+
290
+ if os.getenv('ANTHROPIC_API_KEY'):
291
+ providers.append('anthropic')
292
+
293
+ if os.getenv('GOOGLE_API_KEY') or os.getenv('GEMINI_API_KEY'):
294
+ providers.append('gemini')
295
+
296
+ if os.getenv('XAI_API_KEY') or os.getenv('GROK_API_KEY'):
297
+ providers.append('grok')
298
+
299
+ return providers
300
+
301
+ # ======= Validation Utilities =======
302
+
303
+ def validate_project_name(name: str) -> bool:
304
+ """
305
+ Validate project name for safety and compatibility.
306
+
307
+ Args:
308
+ name: Project name to validate
309
+
310
+ Returns:
311
+ True if name is valid
312
+ """
313
+ if not name:
314
+ return False
315
+
316
+ # Check length
317
+ if len(name) > 50:
318
+ return False
319
+
320
+ # Check for valid characters (alphanumeric, underscore, hyphen)
321
+ import re
322
+ if not re.match(r'^[a-zA-Z0-9_-]+$', name):
323
+ return False
324
+
325
+ # Must start with letter or number
326
+ if not re.match(r'^[a-zA-Z0-9]', name):
327
+ return False
328
+
329
+ return True
330
+
331
+ def validate_component_name(name: str) -> bool:
332
+ """
333
+ Validate agent/workflow name for Python compatibility.
334
+
335
+ Args:
336
+ name: Component name to validate
337
+
338
+ Returns:
339
+ True if name is valid for Python module
340
+ """
341
+ if not name:
342
+ return False
343
+
344
+ # Check length
345
+ if len(name) > 30:
346
+ return False
347
+
348
+ # Check for valid Python identifier characters
349
+ import re
350
+ if not re.match(r'^[a-zA-Z0-9_]+$', name):
351
+ return False
352
+
353
+ # Must start with letter or underscore
354
+ if not re.match(r'^[a-zA-Z_]', name):
355
+ return False
356
+
357
+ # Cannot be Python keywords
358
+ import keyword
359
+ if keyword.iskeyword(name):
360
+ return False
361
+
362
+ return True
363
+
364
+ # ======= Error Handling =======
365
+
366
+ class CLIError(Exception):
367
+ """Base exception for CLI errors."""
368
+ pass
369
+
370
+ class ProjectNotFoundError(CLIError):
371
+ """Raised when not in a Daita project."""
372
+ pass
373
+
374
+ class ConfigurationError(CLIError):
375
+ """Raised for configuration-related errors."""
376
+ pass
377
+
378
+ def handle_cli_error(error: Exception, verbose: bool = False) -> None:
379
+ """
380
+ Handle CLI errors with appropriate user messages.
381
+
382
+ Args:
383
+ error: Exception that occurred
384
+ verbose: Whether to show detailed error information
385
+ """
386
+ if isinstance(error, ProjectNotFoundError):
387
+ print(" Not in a Daita project directory.")
388
+ print(" Run 'daita init' to create a new project.")
389
+
390
+ elif isinstance(error, ConfigurationError):
391
+ print(f" Configuration error: {str(error)}")
392
+
393
+ elif isinstance(error, CLIError):
394
+ print(f" {str(error)}")
395
+
396
+ else:
397
+ if verbose:
398
+ import traceback
399
+ print(f" Unexpected error: {str(error)}")
400
+ print("\nDetailed error:")
401
+ traceback.print_exc()
402
+ else:
403
+ print(f" Error: {str(error)}")
404
+ print(" Use --verbose for more details.")
405
+
406
+ # ======= Freemium Business Model =======
407
+
408
+ def has_daita_api_key() -> bool:
409
+ """
410
+ Check if DAITA_API_KEY is configured for cloud operations.
411
+
412
+ Returns:
413
+ True if DAITA_API_KEY is available
414
+ """
415
+ # First check environment variable
416
+ if os.getenv('DAITA_API_KEY'):
417
+ return True
418
+
419
+ # Try to load from .env file in current directory
420
+ try:
421
+ from dotenv import load_dotenv
422
+ from pathlib import Path
423
+
424
+ # Try loading from current directory
425
+ env_path = Path.cwd() / '.env'
426
+ if env_path.exists():
427
+ load_dotenv(env_path)
428
+
429
+ api_key = os.getenv('DAITA_API_KEY')
430
+ return bool(api_key)
431
+ except ImportError:
432
+ # dotenv is optional, ignore if not installed
433
+ return False
434
+ except Exception as e:
435
+ return False
436
+
437
+ def require_api_key_for_cloud() -> None:
438
+ """
439
+ Enforce API key requirement for cloud operations with upgrade messaging.
440
+
441
+ Raises:
442
+ SystemExit: If no API key is found, shows upgrade message and exits
443
+ """
444
+ if not has_daita_api_key():
445
+ show_upgrade_message()
446
+ raise SystemExit(1)
447
+
448
+ def show_upgrade_message() -> None:
449
+ """
450
+ Display freemium upgrade message for cloud features.
451
+ """
452
+ print("Ready to deploy to the cloud?")
453
+ print("")
454
+ print(" Get your API key at daita-tech.io")
455
+ print(" Then: export DAITA_API_KEY='your-key-here'")
456
+ print("")
457
+ print(" Get insights, monitoring, and 24/7 hosting")
458
+
459
+ def show_local_vs_cloud_help() -> None:
460
+ """
461
+ Show help message explaining local vs cloud features.
462
+ """
463
+ print("")
464
+ print("Daita Command Guide:")
465
+ print("")
466
+ print(" FREE (Local Development):")
467
+ print(" • daita init - Create projects")
468
+ print(" • daita create - Build agents & workflows")
469
+ print(" • daita test - Test locally")
470
+ print(" • daita test --watch - Development mode")
471
+ print("")
472
+ print(" PREMIUM (Cloud Hosting):")
473
+ print(" • daita push - Deploy to cloud")
474
+ print(" • daita status - Monitor deployments")
475
+ print(" • daita logs - View execution logs")
476
+ print(" • daita run - Execute remotely")
477
+ print("")
478
+ print(" Get started: daita-tech.io")
479
+
480
+ def get_freemium_success_message(project_name: str) -> str:
481
+ """
482
+ Generate success message for project initialization with freemium guidance.
483
+
484
+ Args:
485
+ project_name: Name of the created project
486
+
487
+ Returns:
488
+ Formatted success message with next steps
489
+ """
490
+ return f"""Project '{project_name}' created successfully!
491
+
492
+ Next steps:
493
+ 1. cd {project_name}
494
+ 2. daita test # Test locally (always free)
495
+ 3. daita create agent # Add more agents (free)
496
+ 4. daita test --watch # Development mode (free)
497
+
498
+ Ready for production?
499
+ • daita push staging # Deploy to cloud (requires API key)
500
+ • Get your API key at daita-tech.io
501
+ • Start your free trial with full monitoring & insights!"""
502
+
503
+ # Cloud command definitions for enforcement
504
+ CLOUD_COMMANDS = {
505
+ 'push', 'status', 'logs', 'deployments', 'run',
506
+ 'executions', 'execution-logs'
507
+ }
508
+
509
+ def is_cloud_command(command: str) -> bool:
510
+ """
511
+ Check if a command requires cloud/API key access.
512
+
513
+ Args:
514
+ command: Command name to check
515
+
516
+ Returns:
517
+ True if command requires API key
518
+ """
519
+ return command in CLOUD_COMMANDS
520
+
521
+ # ======= Logging Setup =======
522
+
523
+ def setup_cli_logging(level: str = "INFO") -> None:
524
+ """
525
+ Setup logging for CLI operations.
526
+
527
+ Args:
528
+ level: Logging level (DEBUG, INFO, WARNING, ERROR)
529
+ """
530
+ log_level = getattr(logging, level.upper(), logging.INFO)
531
+
532
+ logging.basicConfig(
533
+ level=log_level,
534
+ format="%(levelname)s: %(message)s",
535
+ force=True # Override any existing configuration
536
+ )
537
+
538
+ # Silence verbose third-party loggers
539
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
540
+ logging.getLogger("requests").setLevel(logging.WARNING)
541
+ logging.getLogger("aiohttp").setLevel(logging.WARNING)
@@ -0,0 +1,62 @@
1
+ """
2
+ Configuration system for Daita Agents.
3
+
4
+ Simplified configuration classes focused on essential functionality.
5
+
6
+ Core configuration classes:
7
+ - AgentConfig: Configuration for individual agents
8
+
9
+ - DaitaConfig: Overall framework configuration
10
+ - RetryPolicy: Simple retry behavior configuration
11
+
12
+ Enums and types:
13
+ - AgentType: Types of available agents
14
+ - FocusType: Types of focus selectors
15
+ - RetryStrategy: Retry timing strategies
16
+
17
+ Usage:
18
+ ```python
19
+ from daita.config import AgentConfig, RetryPolicy
20
+
21
+ # Simple agent configuration
22
+ config = AgentConfig(name="My Agent")
23
+
24
+ # Agent with retry enabled
25
+ config = AgentConfig(
26
+ name="My Agent",
27
+ enable_retry=True,
28
+ retry_policy=RetryPolicy(max_retries=5)
29
+ )
30
+ ```
31
+ """
32
+
33
+ from .base import (
34
+ # Enums
35
+ AgentType,
36
+ FocusType,
37
+ RetryStrategy,
38
+
39
+ # Configuration classes
40
+ FocusConfig,
41
+ RetryPolicy,
42
+ AgentConfig,
43
+ DaitaConfig,
44
+ )
45
+
46
+ from .settings import settings
47
+
48
+ __all__ = [
49
+ # Enums
50
+ "AgentType",
51
+ "FocusType",
52
+ "RetryStrategy",
53
+
54
+ # Configuration classes
55
+ "FocusConfig",
56
+ "RetryPolicy",
57
+ "AgentConfig",
58
+ "DaitaConfig",
59
+
60
+ # Runtime settings
61
+ "settings",
62
+ ]