erdo 0.1.4__py3-none-any.whl → 0.1.5__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 erdo might be problematic. Click here for more details.
- erdo/__init__.py +14 -11
- erdo/_generated/actions/__init__.py +3 -1
- erdo/_generated/actions/analysis.py +116 -4
- erdo/_generated/actions/bot.py +39 -3
- erdo/_generated/actions/codeexec.py +53 -28
- erdo/_generated/actions/llm.py +22 -1
- erdo/_generated/actions/memory.py +252 -57
- erdo/_generated/actions/pdfextractor.py +97 -0
- erdo/_generated/actions/resource_definitions.py +114 -12
- erdo/_generated/actions/sqlexec.py +86 -0
- erdo/_generated/actions/utils.py +178 -56
- erdo/_generated/actions/webparser.py +15 -5
- erdo/_generated/actions/websearch.py +15 -5
- erdo/_generated/condition/__init__.py +139 -129
- erdo/_generated/types.py +92 -48
- erdo/actions/__init__.py +9 -10
- erdo/bot_permissions.py +266 -0
- erdo/config/__init__.py +5 -0
- erdo/config/config.py +140 -0
- erdo/invoke/__init__.py +10 -0
- erdo/invoke/client.py +213 -0
- erdo/invoke/invoke.py +238 -0
- erdo/sync/__init__.py +17 -0
- erdo/sync/client.py +95 -0
- erdo/sync/extractor.py +476 -0
- erdo/sync/sync.py +328 -0
- erdo/types.py +508 -15
- {erdo-0.1.4.dist-info → erdo-0.1.5.dist-info}/METADATA +2 -1
- erdo-0.1.5.dist-info/RECORD +45 -0
- erdo-0.1.4.dist-info/RECORD +0 -33
- {erdo-0.1.4.dist-info → erdo-0.1.5.dist-info}/WHEEL +0 -0
- {erdo-0.1.4.dist-info → erdo-0.1.5.dist-info}/entry_points.txt +0 -0
- {erdo-0.1.4.dist-info → erdo-0.1.5.dist-info}/licenses/LICENSE +0 -0
erdo/sync/sync.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Main sync functionality for syncing agents to the backend."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from .client import SyncClient
|
|
8
|
+
from .extractor import (
|
|
9
|
+
extract_agent_from_instance,
|
|
10
|
+
extract_agents_from_file,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SyncResult:
|
|
16
|
+
"""Result of a sync operation."""
|
|
17
|
+
|
|
18
|
+
success: bool
|
|
19
|
+
bot_id: Optional[str] = None
|
|
20
|
+
bot_name: Optional[str] = None
|
|
21
|
+
error: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
if self.success:
|
|
25
|
+
return f"✅ Successfully synced {self.bot_name} (ID: {self.bot_id})"
|
|
26
|
+
else:
|
|
27
|
+
return f"❌ Failed to sync {self.bot_name}: {self.error}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Sync:
|
|
31
|
+
"""Main sync class for syncing agents."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
agent: Optional[Any] = None,
|
|
36
|
+
endpoint: Optional[str] = None,
|
|
37
|
+
auth_token: Optional[str] = None,
|
|
38
|
+
):
|
|
39
|
+
"""Initialize sync and optionally sync an agent immediately.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
agent: Optional Agent instance to sync immediately
|
|
43
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
44
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
45
|
+
"""
|
|
46
|
+
self.client = SyncClient(endpoint=endpoint, auth_token=auth_token)
|
|
47
|
+
self.result = None
|
|
48
|
+
|
|
49
|
+
# If an agent is provided, sync it immediately
|
|
50
|
+
if agent:
|
|
51
|
+
self.result = self.sync_agent(agent)
|
|
52
|
+
|
|
53
|
+
def sync_agent(
|
|
54
|
+
self, agent: Any, source_file_path: Optional[str] = None
|
|
55
|
+
) -> SyncResult:
|
|
56
|
+
"""Sync a single agent to the backend.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
agent: Agent instance to sync
|
|
60
|
+
source_file_path: Optional path to the source file for better extraction
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
SyncResult with the outcome of the sync operation
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
# Extract agent data
|
|
67
|
+
agent_data = extract_agent_from_instance(agent, source_file_path)
|
|
68
|
+
|
|
69
|
+
# Convert to API format
|
|
70
|
+
bot_request = self._convert_to_api_format(agent_data)
|
|
71
|
+
|
|
72
|
+
# Send to backend
|
|
73
|
+
bot_id = self.client.upsert_bot(bot_request)
|
|
74
|
+
|
|
75
|
+
return SyncResult(success=True, bot_id=bot_id, bot_name=agent.name)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return SyncResult(
|
|
79
|
+
success=False, bot_name=getattr(agent, "name", "Unknown"), error=str(e)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_file(
|
|
84
|
+
cls,
|
|
85
|
+
file_path: str,
|
|
86
|
+
endpoint: Optional[str] = None,
|
|
87
|
+
auth_token: Optional[str] = None,
|
|
88
|
+
) -> List[SyncResult]:
|
|
89
|
+
"""Sync agents from a Python file.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_path: Path to the Python file containing agents
|
|
93
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
94
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
List of SyncResult objects for each agent found
|
|
98
|
+
"""
|
|
99
|
+
sync = cls(endpoint=endpoint, auth_token=auth_token)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Extract agents from file
|
|
103
|
+
agent_data = extract_agents_from_file(file_path)
|
|
104
|
+
|
|
105
|
+
# Handle single agent or list of agents
|
|
106
|
+
if isinstance(agent_data, list):
|
|
107
|
+
results = []
|
|
108
|
+
for data in agent_data:
|
|
109
|
+
result = sync._sync_agent_data(data)
|
|
110
|
+
results.append(result)
|
|
111
|
+
return results
|
|
112
|
+
else:
|
|
113
|
+
result = sync._sync_agent_data(agent_data)
|
|
114
|
+
return [result]
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return [
|
|
118
|
+
SyncResult(
|
|
119
|
+
success=False, error=f"Failed to extract agents from file: {e}"
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_directory(
|
|
125
|
+
cls,
|
|
126
|
+
directory_path: str = ".",
|
|
127
|
+
endpoint: Optional[str] = None,
|
|
128
|
+
auth_token: Optional[str] = None,
|
|
129
|
+
) -> List[SyncResult]:
|
|
130
|
+
"""Sync all agents from a directory.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
directory_path: Path to directory containing agent files (default: current directory)
|
|
134
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
135
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of SyncResult objects for each agent found
|
|
139
|
+
"""
|
|
140
|
+
sync = cls(endpoint=endpoint, auth_token=auth_token)
|
|
141
|
+
results = []
|
|
142
|
+
|
|
143
|
+
directory = Path(directory_path)
|
|
144
|
+
|
|
145
|
+
# Check for __init__.py with agents first
|
|
146
|
+
init_file = directory / "__init__.py"
|
|
147
|
+
if init_file.exists():
|
|
148
|
+
try:
|
|
149
|
+
init_results = cls.from_file(
|
|
150
|
+
str(init_file), endpoint=endpoint, auth_token=auth_token
|
|
151
|
+
)
|
|
152
|
+
if init_results:
|
|
153
|
+
return init_results
|
|
154
|
+
except:
|
|
155
|
+
pass # Fall back to directory scan
|
|
156
|
+
|
|
157
|
+
# Scan directory for agent files
|
|
158
|
+
for py_file in directory.glob("**/*.py"):
|
|
159
|
+
# Skip common non-agent files
|
|
160
|
+
if any(
|
|
161
|
+
skip in str(py_file)
|
|
162
|
+
for skip in ["__pycache__", "test_", "_test.py", ".venv", "venv"]
|
|
163
|
+
):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
# Check if file has agents
|
|
168
|
+
with open(py_file, "r") as f:
|
|
169
|
+
content = f.read()
|
|
170
|
+
if "agents = [" not in content:
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# Try to sync agents from this file
|
|
174
|
+
file_results = cls.from_file(
|
|
175
|
+
str(py_file), endpoint=endpoint, auth_token=auth_token
|
|
176
|
+
)
|
|
177
|
+
results.extend(file_results)
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
results.append(
|
|
181
|
+
SyncResult(success=False, error=f"Failed to process {py_file}: {e}")
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return results
|
|
185
|
+
|
|
186
|
+
def _sync_agent_data(self, agent_data: Dict[str, Any]) -> SyncResult:
|
|
187
|
+
"""Sync extracted agent data to the backend.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
agent_data: Extracted agent data dictionary
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
SyncResult with the outcome of the sync operation
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
# Convert to API format
|
|
197
|
+
bot_request = self._convert_to_api_format(agent_data)
|
|
198
|
+
|
|
199
|
+
# Send to backend
|
|
200
|
+
bot_id = self.client.upsert_bot(bot_request)
|
|
201
|
+
|
|
202
|
+
return SyncResult(
|
|
203
|
+
success=True, bot_id=bot_id, bot_name=agent_data["bot"]["name"]
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
return SyncResult(
|
|
208
|
+
success=False,
|
|
209
|
+
bot_name=agent_data.get("bot", {}).get("name", "Unknown"),
|
|
210
|
+
error=str(e),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def _convert_to_api_format(self, agent_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
214
|
+
"""Convert extracted agent data to API format.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
agent_data: Extracted agent data
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Bot request in API format
|
|
221
|
+
"""
|
|
222
|
+
# Override source to "user" as per Go implementation
|
|
223
|
+
bot_data = agent_data["bot"].copy()
|
|
224
|
+
bot_data["source"] = "user"
|
|
225
|
+
|
|
226
|
+
# Convert steps to API format
|
|
227
|
+
api_steps = []
|
|
228
|
+
for step_with_handlers in agent_data.get("steps", []):
|
|
229
|
+
api_step = self._convert_step_to_api(step_with_handlers)
|
|
230
|
+
api_steps.append(api_step)
|
|
231
|
+
|
|
232
|
+
# Build the API request
|
|
233
|
+
# Convert parameter_definitions to dicts if they're objects
|
|
234
|
+
param_defs = agent_data.get("parameter_definitions", [])
|
|
235
|
+
if param_defs and hasattr(param_defs[0], "to_dict"):
|
|
236
|
+
param_defs = [
|
|
237
|
+
pd.to_dict() if hasattr(pd, "to_dict") else pd for pd in param_defs
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
bot_request = {
|
|
241
|
+
"bot": bot_data,
|
|
242
|
+
"steps": api_steps,
|
|
243
|
+
"source": "user",
|
|
244
|
+
"parameter_definitions": param_defs,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Include action result schemas if present
|
|
248
|
+
if "action_result_schemas" in agent_data:
|
|
249
|
+
bot_request["action_result_schemas"] = agent_data["action_result_schemas"]
|
|
250
|
+
|
|
251
|
+
return bot_request
|
|
252
|
+
|
|
253
|
+
def _convert_step_to_api(
|
|
254
|
+
self, step_with_handlers: Dict[str, Any]
|
|
255
|
+
) -> Dict[str, Any]:
|
|
256
|
+
"""Convert StepWithHandlers to API format.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
step_with_handlers: Step with handlers dictionary
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Step in API format
|
|
263
|
+
"""
|
|
264
|
+
# Ensure parameters is not None
|
|
265
|
+
step = step_with_handlers["step"].copy()
|
|
266
|
+
if step.get("parameters") is None:
|
|
267
|
+
step["parameters"] = {}
|
|
268
|
+
|
|
269
|
+
# Convert result handlers
|
|
270
|
+
api_handlers = []
|
|
271
|
+
for handler in step_with_handlers.get("result_handlers", []):
|
|
272
|
+
api_handler = handler.copy()
|
|
273
|
+
|
|
274
|
+
# Convert nested steps in handler
|
|
275
|
+
if "steps" in api_handler:
|
|
276
|
+
api_handler_steps = []
|
|
277
|
+
for nested_step in api_handler["steps"]:
|
|
278
|
+
api_nested = self._convert_step_to_api(nested_step)
|
|
279
|
+
api_handler_steps.append(api_nested)
|
|
280
|
+
api_handler["steps"] = api_handler_steps
|
|
281
|
+
|
|
282
|
+
api_handlers.append(api_handler)
|
|
283
|
+
|
|
284
|
+
return {"step": step, "result_handlers": api_handlers}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Convenience functions
|
|
288
|
+
def sync_agent(
|
|
289
|
+
agent: Any, source_file_path: Optional[str] = None, **kwargs
|
|
290
|
+
) -> SyncResult:
|
|
291
|
+
"""Sync a single agent to the backend.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
agent: Agent instance to sync
|
|
295
|
+
source_file_path: Optional path to the source file
|
|
296
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
SyncResult with the outcome of the sync operation
|
|
300
|
+
"""
|
|
301
|
+
sync = Sync(endpoint=kwargs.get("endpoint"), auth_token=kwargs.get("auth_token"))
|
|
302
|
+
return sync.sync_agent(agent, source_file_path)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def sync_agents_from_file(file_path: str, **kwargs) -> List[SyncResult]:
|
|
306
|
+
"""Sync agents from a Python file.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
file_path: Path to the Python file containing agents
|
|
310
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
List of SyncResult objects
|
|
314
|
+
"""
|
|
315
|
+
return Sync.from_file(file_path, **kwargs)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def sync_agents_from_directory(directory_path: str = ".", **kwargs) -> List[SyncResult]:
|
|
319
|
+
"""Sync all agents from a directory.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
directory_path: Path to directory containing agent files
|
|
323
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of SyncResult objects
|
|
327
|
+
"""
|
|
328
|
+
return Sync.from_directory(directory_path, **kwargs)
|