erdo 0.1.31__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 (48) hide show
  1. erdo/__init__.py +35 -0
  2. erdo/_generated/__init__.py +18 -0
  3. erdo/_generated/actions/__init__.py +34 -0
  4. erdo/_generated/actions/analysis.py +179 -0
  5. erdo/_generated/actions/bot.py +186 -0
  6. erdo/_generated/actions/codeexec.py +199 -0
  7. erdo/_generated/actions/llm.py +148 -0
  8. erdo/_generated/actions/memory.py +463 -0
  9. erdo/_generated/actions/pdfextractor.py +97 -0
  10. erdo/_generated/actions/resource_definitions.py +296 -0
  11. erdo/_generated/actions/sqlexec.py +90 -0
  12. erdo/_generated/actions/utils.py +475 -0
  13. erdo/_generated/actions/webparser.py +119 -0
  14. erdo/_generated/actions/websearch.py +85 -0
  15. erdo/_generated/condition/__init__.py +556 -0
  16. erdo/_generated/internal.py +51 -0
  17. erdo/_generated/internal_actions.py +91 -0
  18. erdo/_generated/parameters.py +17 -0
  19. erdo/_generated/secrets.py +17 -0
  20. erdo/_generated/template_functions.py +55 -0
  21. erdo/_generated/types.py +3907 -0
  22. erdo/actions/__init__.py +40 -0
  23. erdo/bot_permissions.py +266 -0
  24. erdo/cli_entry.py +73 -0
  25. erdo/conditions/__init__.py +11 -0
  26. erdo/config/__init__.py +5 -0
  27. erdo/config/config.py +140 -0
  28. erdo/formatting.py +279 -0
  29. erdo/install_cli.py +140 -0
  30. erdo/integrations.py +131 -0
  31. erdo/invoke/__init__.py +11 -0
  32. erdo/invoke/client.py +234 -0
  33. erdo/invoke/invoke.py +555 -0
  34. erdo/state.py +376 -0
  35. erdo/sync/__init__.py +17 -0
  36. erdo/sync/client.py +95 -0
  37. erdo/sync/extractor.py +492 -0
  38. erdo/sync/sync.py +327 -0
  39. erdo/template.py +136 -0
  40. erdo/test/__init__.py +41 -0
  41. erdo/test/evaluate.py +272 -0
  42. erdo/test/runner.py +263 -0
  43. erdo/types.py +1431 -0
  44. erdo-0.1.31.dist-info/METADATA +471 -0
  45. erdo-0.1.31.dist-info/RECORD +48 -0
  46. erdo-0.1.31.dist-info/WHEEL +4 -0
  47. erdo-0.1.31.dist-info/entry_points.txt +2 -0
  48. erdo-0.1.31.dist-info/licenses/LICENSE +22 -0
erdo/formatting.py ADDED
@@ -0,0 +1,279 @@
1
+ """Output formatting helpers for bot invocations.
2
+
3
+ This module provides utilities to parse and format bot invocation events
4
+ into human-readable output. Use these for displaying invocation results
5
+ in terminals, scripts, or other user-facing contexts.
6
+
7
+ Example:
8
+ >>> from erdo import invoke
9
+ >>> from erdo.formatting import format_invocation
10
+ >>>
11
+ >>> response = invoke("my_agent", messages=[...])
12
+ >>> print(format_invocation(response))
13
+ Bot: my agent
14
+ Invocation ID: abc-123
15
+
16
+ Result:
17
+ The answer is 4
18
+
19
+ >>> # Verbose mode shows steps
20
+ >>> print(format_invocation(response, verbose=True))
21
+ Bot: my agent
22
+ Invocation ID: abc-123
23
+
24
+ Steps:
25
+ ✓ step1 (utils.echo)
26
+ ✓ step2 (llm.message)
27
+
28
+ Result:
29
+ The answer is 4
30
+ """
31
+
32
+ import json
33
+ from dataclasses import dataclass, field
34
+ from typing import Any, Dict, List, Optional
35
+
36
+
37
+ @dataclass
38
+ class StepInfo:
39
+ """Information about a single step execution."""
40
+
41
+ key: str
42
+ action: str
43
+ status: str = "completed"
44
+
45
+
46
+ @dataclass
47
+ class InvocationSummary:
48
+ """Structured summary of a bot invocation.
49
+
50
+ This provides a clean, parsed view of the invocation events
51
+ with key information extracted and organized.
52
+ """
53
+
54
+ bot_name: Optional[str] = None
55
+ bot_key: Optional[str] = None
56
+ invocation_id: Optional[str] = None
57
+ steps: List[StepInfo] = field(default_factory=list)
58
+ result: Optional[Any] = None
59
+ error: Optional[str] = None
60
+ success: bool = True
61
+
62
+
63
+ def parse_invocation_events(
64
+ events: List[Dict[str, Any]],
65
+ bot_key: Optional[str] = None,
66
+ invocation_id: Optional[str] = None,
67
+ ) -> InvocationSummary:
68
+ """Parse raw invocation events into a structured summary.
69
+
70
+ Args:
71
+ events: List of event dictionaries from the backend
72
+ bot_key: Bot key to use as fallback if not in events
73
+ invocation_id: Invocation ID to use as fallback if not in events
74
+
75
+ Returns:
76
+ InvocationSummary with parsed information
77
+
78
+ Example:
79
+ >>> summary = parse_invocation_events(response.events, bot_key="my_agent")
80
+ >>> print(summary.bot_name)
81
+ 'my agent'
82
+ >>> print(summary.steps)
83
+ [StepInfo(key='step1', action='utils.echo', status='completed')]
84
+ """
85
+ summary = InvocationSummary(
86
+ bot_key=bot_key,
87
+ invocation_id=invocation_id,
88
+ )
89
+
90
+ steps_seen = set()
91
+ final_messages = []
92
+
93
+ for event in events:
94
+ # Events are dicts with 'payload' and 'metadata'
95
+ payload = event.get("payload", {})
96
+ metadata = event.get("metadata", {})
97
+
98
+ # Extract invocation ID if not set
99
+ if summary.invocation_id is None:
100
+ if "invocation_id" in payload:
101
+ summary.invocation_id = payload["invocation_id"]
102
+ elif "invocation_id" in metadata:
103
+ summary.invocation_id = metadata["invocation_id"]
104
+
105
+ # Extract bot name
106
+ if summary.bot_name is None and "bot_name" in payload:
107
+ summary.bot_name = payload["bot_name"]
108
+
109
+ # Track step execution
110
+ if "action_type" in payload and "key" in payload:
111
+ step_key = payload["key"]
112
+ # Only add if not already present (avoid duplicates)
113
+ if step_key not in steps_seen:
114
+ steps_seen.add(step_key)
115
+ summary.steps.append(
116
+ StepInfo(
117
+ key=step_key,
118
+ action=payload["action_type"],
119
+ status="completed",
120
+ )
121
+ )
122
+
123
+ # Collect user-visible text messages (final output)
124
+ if isinstance(payload, str) and metadata.get("user_visibility") == "visible":
125
+ if metadata.get("message_content_id"): # It's part of a message
126
+ final_messages.append(payload)
127
+
128
+ # Combine final messages into result
129
+ if final_messages:
130
+ summary.result = "".join(final_messages)
131
+
132
+ return summary
133
+
134
+
135
+ def format_invocation(
136
+ response: Any,
137
+ mode: str = "text",
138
+ verbose: bool = False,
139
+ ) -> str:
140
+ """Format a bot invocation response for display.
141
+
142
+ Args:
143
+ response: InvokeResult from invoke()
144
+ mode: Output mode - 'text' or 'json'
145
+ verbose: Show detailed step execution (only applies to text mode)
146
+
147
+ Returns:
148
+ Formatted string ready to print
149
+
150
+ Example:
151
+ >>> from erdo import invoke
152
+ >>> from erdo.formatting import format_invocation
153
+ >>>
154
+ >>> response = invoke("my_agent", messages=[...])
155
+ >>>
156
+ >>> # Simple text output
157
+ >>> print(format_invocation(response))
158
+ >>>
159
+ >>> # Verbose text output (shows steps)
160
+ >>> print(format_invocation(response, verbose=True))
161
+ >>>
162
+ >>> # JSON output
163
+ >>> print(format_invocation(response, mode='json'))
164
+ """
165
+ if mode == "json":
166
+ return _format_as_json(response)
167
+ else:
168
+ return _format_as_text(response, verbose)
169
+
170
+
171
+ def _format_as_json(response: Any) -> str:
172
+ """Format response as JSON."""
173
+ output = {
174
+ "success": response.success,
175
+ "invocation_id": response.invocation_id,
176
+ "result": response.result,
177
+ "error": response.error,
178
+ "events": response.events,
179
+ }
180
+ return json.dumps(output, indent=2)
181
+
182
+
183
+ def _format_as_text(response: Any, verbose: bool = False) -> str:
184
+ """Format response as human-readable text."""
185
+ # Get bot key from response if available
186
+ bot_key = getattr(response, "bot_id", None)
187
+ invocation_id = response.invocation_id
188
+
189
+ # Parse events to extract information
190
+ # Handle SDK response structure: response.result may contain {'events': [...]}
191
+ events_to_process = response.events
192
+ if isinstance(response.result, dict) and "events" in response.result:
193
+ events_to_process = response.result["events"]
194
+
195
+ summary = parse_invocation_events(
196
+ events_to_process,
197
+ bot_key=bot_key,
198
+ invocation_id=invocation_id,
199
+ )
200
+
201
+ # Build output
202
+ lines = []
203
+
204
+ if not response.success:
205
+ lines.append(f"❌ Invocation failed: {response.error}")
206
+ return "\n".join(lines)
207
+
208
+ # Header
209
+ lines.append(f"Bot: {summary.bot_name or summary.bot_key or 'unknown'}")
210
+ lines.append(f"Invocation ID: {summary.invocation_id or 'N/A'}")
211
+
212
+ # Steps (verbose mode)
213
+ if verbose and summary.steps:
214
+ lines.append("")
215
+ lines.append("Steps:")
216
+ for step in summary.steps:
217
+ status_icon = "✓" if step.status == "completed" else "•"
218
+ lines.append(f" {status_icon} {step.key} ({step.action})")
219
+
220
+ # Result
221
+ if summary.result:
222
+ lines.append("")
223
+ lines.append("Result:")
224
+
225
+ # Try to parse as JSON for pretty printing
226
+ try:
227
+ parsed = json.loads(summary.result)
228
+ lines.append(json.dumps(parsed, indent=2))
229
+ except (json.JSONDecodeError, TypeError):
230
+ # If not JSON, print as is
231
+ lines.append(summary.result)
232
+ elif response.result is not None:
233
+ lines.append("")
234
+ lines.append("Result:")
235
+ if isinstance(response.result, (dict, list)):
236
+ lines.append(json.dumps(response.result, indent=2))
237
+ else:
238
+ lines.append(str(response.result))
239
+
240
+ return "\n".join(lines)
241
+
242
+
243
+ # Convenience method to add to InvokeResult
244
+ def add_format_method():
245
+ """Add format() method to InvokeResult class.
246
+
247
+ This is called during SDK initialization to add the format()
248
+ method to InvokeResult instances.
249
+ """
250
+ try:
251
+ from .invoke.invoke import InvokeResult
252
+
253
+ def format_method(self, mode="text", verbose=False):
254
+ """Format this invocation result for display.
255
+
256
+ Args:
257
+ mode: 'text' or 'json'
258
+ verbose: Show step details (text mode only)
259
+
260
+ Returns:
261
+ Formatted string
262
+
263
+ Example:
264
+ >>> response = invoke("my_agent", messages=[...])
265
+ >>> print(response.format())
266
+ >>> print(response.format(verbose=True))
267
+ """
268
+ return format_invocation(self, mode=mode, verbose=verbose)
269
+
270
+ # Add method to class
271
+ InvokeResult.format = format_method
272
+
273
+ except ImportError:
274
+ # InvokeResult not available, skip
275
+ pass
276
+
277
+
278
+ # Auto-add format method on import
279
+ add_format_method()
erdo/install_cli.py ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+ """Post-install script to download Erdo CLI binary from GitHub releases."""
3
+
4
+ import os
5
+ import platform
6
+ import shutil
7
+ import urllib.request
8
+ from pathlib import Path
9
+
10
+
11
+ def get_platform_info():
12
+ """Get platform and architecture information."""
13
+ system = platform.system().lower()
14
+ machine = platform.machine().lower()
15
+
16
+ # Normalize architecture names
17
+ if machine in ["x86_64", "amd64"]:
18
+ arch = "amd64"
19
+ elif machine in ["aarch64", "arm64"]:
20
+ arch = "arm64"
21
+ else:
22
+ raise RuntimeError(f"Unsupported architecture: {machine}")
23
+
24
+ # Map platform names
25
+ if system == "darwin":
26
+ platform_name = "darwin"
27
+ elif system == "linux":
28
+ platform_name = "linux"
29
+ elif system == "windows":
30
+ platform_name = "windows"
31
+ else:
32
+ raise RuntimeError(f"Unsupported platform: {system}")
33
+
34
+ return platform_name, arch
35
+
36
+
37
+ def get_download_url(version="latest"):
38
+ """Get the download URL for the CLI binary."""
39
+ platform_name, arch = get_platform_info()
40
+
41
+ # Construct the binary name based on GoReleaser naming convention
42
+ if platform_name == "windows":
43
+ binary_name = f"erdo-cli_Windows_{arch}.zip"
44
+ else:
45
+ # For Unix-like systems, use tar.gz
46
+ if arch == "amd64":
47
+ arch_name = "x86_64"
48
+ else:
49
+ arch_name = arch
50
+
51
+ platform_title = platform_name.title()
52
+ binary_name = f"erdo-cli_{platform_title}_{arch_name}.tar.gz"
53
+
54
+ base_url = "https://github.com/erdoai/homebrew-tap/releases"
55
+ if version == "latest":
56
+ return f"{base_url}/latest/download/{binary_name}"
57
+ else:
58
+ return f"{base_url}/download/{version}/{binary_name}"
59
+
60
+
61
+ def download_and_install_cli():
62
+ """Download and install the CLI binary."""
63
+ try:
64
+ # Get the installation directory
65
+ package_dir = Path(__file__).parent
66
+ bin_dir = package_dir / "bin"
67
+ bin_dir.mkdir(exist_ok=True)
68
+
69
+ platform_name, arch = get_platform_info()
70
+
71
+ # Determine the final binary name
72
+ if platform_name == "windows":
73
+ binary_name = "erdo.exe"
74
+ else:
75
+ binary_name = "erdo"
76
+
77
+ binary_path = bin_dir / binary_name
78
+
79
+ # Skip if binary already exists
80
+ if binary_path.exists():
81
+ print(f"Erdo CLI already installed at {binary_path}")
82
+ return
83
+
84
+ print("Downloading Erdo CLI...")
85
+ download_url = get_download_url()
86
+
87
+ # Download the archive
88
+ archive_path = bin_dir / "erdo-cli-archive"
89
+ urllib.request.urlretrieve(download_url, archive_path)
90
+
91
+ # Extract the binary
92
+ if platform_name == "windows":
93
+ import zipfile
94
+
95
+ with zipfile.ZipFile(archive_path, "r") as zip_ref:
96
+ # Extract erdo.exe from the zip
97
+ for file_info in zip_ref.filelist:
98
+ if file_info.filename.endswith("erdo.exe"):
99
+ with (
100
+ zip_ref.open(file_info) as source,
101
+ open(binary_path, "wb") as target,
102
+ ):
103
+ shutil.copyfileobj(source, target)
104
+ break
105
+ else:
106
+ import tarfile
107
+
108
+ with tarfile.open(archive_path, "r:gz") as tar_ref:
109
+ # Extract erdo binary from the tar.gz
110
+ for member in tar_ref.getmembers():
111
+ if member.name.endswith("/erdo") or member.name == "erdo":
112
+ with (
113
+ tar_ref.extractfile(member) as source,
114
+ open(binary_path, "wb") as target,
115
+ ):
116
+ shutil.copyfileobj(source, target)
117
+ break
118
+
119
+ # Make executable on Unix-like systems
120
+ if platform_name != "windows":
121
+ os.chmod(binary_path, 0o755)
122
+
123
+ # Clean up the archive
124
+ archive_path.unlink()
125
+
126
+ print(f"✓ Erdo CLI installed to {binary_path}")
127
+
128
+ # Add to PATH instructions
129
+ print("\nTo use the CLI globally, add the following to your PATH:")
130
+ print(f'export PATH="{bin_dir}:$PATH"')
131
+
132
+ except Exception as e:
133
+ print(f"Warning: Failed to download Erdo CLI: {e}")
134
+ print(
135
+ "You can manually download it from: https://github.com/erdoai/homebrew-tap/releases"
136
+ )
137
+
138
+
139
+ if __name__ == "__main__":
140
+ download_and_install_cli()
erdo/integrations.py ADDED
@@ -0,0 +1,131 @@
1
+ # Integration base classes for defining integration configurations in Python
2
+ """
3
+ Integration configuration framework for defining integration configs alongside agents.
4
+
5
+ This module provides base classes for defining integration configurations in Python,
6
+ which can then be synced to the backend alongside agents.
7
+ """
8
+
9
+ from typing import Any, Dict, Optional
10
+
11
+ from ._generated.types import IntegrationConfig, IntegrationDefinition
12
+
13
+
14
+ # Helper function to create integration configurations
15
+ def create_integration_config(**kwargs) -> IntegrationConfig:
16
+ """Create an IntegrationConfig with the provided parameters."""
17
+ # Extract metadata
18
+ source = kwargs.pop("source", "python")
19
+ file_path = kwargs.pop("file_path", None)
20
+
21
+ # Create IntegrationDefinition with remaining parameters
22
+ definition = IntegrationDefinition(**kwargs)
23
+
24
+ return IntegrationConfig(definition=definition, source=source, file_path=file_path)
25
+
26
+
27
+ # Base class that acts like IntegrationConfig but handles initialization better
28
+ class IntegrationConfigClass:
29
+ """Base class for integration configurations using auto-generated types."""
30
+
31
+ def __init__(self, **kwargs):
32
+ """Initialize with integration definition parameters."""
33
+ # Extract metadata
34
+ self._source = kwargs.pop("source", "python")
35
+ self._file_path = kwargs.pop("file_path", None)
36
+
37
+ # Create the definition with remaining kwargs
38
+ self._definition = IntegrationDefinition(**kwargs)
39
+
40
+ # Create the actual IntegrationConfig instance
41
+ self._config = IntegrationConfig(
42
+ definition=self._definition, source=self._source, file_path=self._file_path
43
+ )
44
+
45
+ def to_dict(self) -> Dict[str, Any]:
46
+ """Convert to dictionary for syncing to backend."""
47
+ return self._config.to_dict()
48
+
49
+ @property
50
+ def definition(self) -> IntegrationDefinition:
51
+ """Access the underlying definition."""
52
+ return self._definition
53
+
54
+ @property
55
+ def config(self) -> IntegrationConfig:
56
+ """Access the underlying IntegrationConfig."""
57
+ return self._config
58
+
59
+
60
+ # Helper functions for common credential schemas
61
+ def oauth_credential_schema() -> Dict[str, Any]:
62
+ """Helper to create standard OAuth credential schema"""
63
+ return {
64
+ "access_token": {
65
+ "type": "string",
66
+ "description": "OAuth access token",
67
+ "required": True,
68
+ "source": "integration_credentials",
69
+ },
70
+ "refresh_token": {
71
+ "type": "string",
72
+ "description": "OAuth refresh token",
73
+ "required": False,
74
+ "source": "integration_credentials",
75
+ },
76
+ }
77
+
78
+
79
+ def database_credential_schema() -> Dict[str, Any]:
80
+ """Helper to create standard database credential schema"""
81
+ return {
82
+ "host": {
83
+ "type": "string",
84
+ "description": "Database host",
85
+ "required": True,
86
+ "source": "integration_credentials",
87
+ },
88
+ "port": {
89
+ "type": "string",
90
+ "description": "Database port",
91
+ "required": True,
92
+ "source": "integration_credentials",
93
+ },
94
+ "database": {
95
+ "type": "string",
96
+ "description": "Database name",
97
+ "required": True,
98
+ "source": "integration_credentials",
99
+ },
100
+ "username": {
101
+ "type": "string",
102
+ "description": "Database username",
103
+ "required": True,
104
+ "source": "integration_credentials",
105
+ },
106
+ "password": {
107
+ "type": "string",
108
+ "description": "Database password",
109
+ "required": True,
110
+ "source": "integration_credentials",
111
+ },
112
+ }
113
+
114
+
115
+ def api_key_credential_schema(
116
+ key_name: str = "api_key", header_name: Optional[str] = None
117
+ ) -> Dict[str, Any]:
118
+ """Helper to create standard API key credential schema"""
119
+ schema = {
120
+ key_name: {
121
+ "type": "string",
122
+ "description": "API Key for authentication",
123
+ "required": True,
124
+ "source": "integration_credentials",
125
+ }
126
+ }
127
+
128
+ if header_name:
129
+ schema[key_name]["header"] = header_name
130
+
131
+ return schema
@@ -0,0 +1,11 @@
1
+ """Invoke module for running agents via the Erdo orchestrator."""
2
+
3
+ from .invoke import Invoke, InvokeResult, invoke, invoke_agent, invoke_by_key
4
+
5
+ __all__ = [
6
+ "Invoke",
7
+ "InvokeResult",
8
+ "invoke",
9
+ "invoke_agent",
10
+ "invoke_by_key",
11
+ ]