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/bot_permissions.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Bot permission management for the Erdo SDK."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BotPermissions:
|
|
10
|
+
"""Manage RBAC permissions for bots."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: Optional[str] = None):
|
|
13
|
+
"""Initialize bot permissions manager.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
base_url: Erdo server base URL. Defaults to ERDO_SERVER_URL env var or localhost:4000
|
|
17
|
+
"""
|
|
18
|
+
self.base_url = base_url or os.getenv(
|
|
19
|
+
"ERDO_SERVER_URL", "http://localhost:4000"
|
|
20
|
+
)
|
|
21
|
+
self.session = requests.Session()
|
|
22
|
+
|
|
23
|
+
# Set auth token if available
|
|
24
|
+
token = os.getenv("ERDO_AUTH_TOKEN")
|
|
25
|
+
if token:
|
|
26
|
+
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
|
27
|
+
|
|
28
|
+
def set_public_access(
|
|
29
|
+
self, bot_id: str, is_public: bool = True, permission_level: str = "view"
|
|
30
|
+
) -> bool:
|
|
31
|
+
"""Set public access for a bot.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
bot_id: Bot ID
|
|
35
|
+
is_public: Whether to make the bot public
|
|
36
|
+
permission_level: Permission level for public access (view, comment, edit, admin)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if successful, False otherwise
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/public"
|
|
43
|
+
data = {
|
|
44
|
+
"isPublic": is_public,
|
|
45
|
+
"level": permission_level if is_public else None,
|
|
46
|
+
}
|
|
47
|
+
response = self.session.put(url, json=data)
|
|
48
|
+
return response.status_code == 200
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"Error setting public access: {e}")
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def set_user_permission(
|
|
54
|
+
self, bot_id: str, user_id: str, permission_level: str
|
|
55
|
+
) -> bool:
|
|
56
|
+
"""Set permissions for a specific user.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
bot_id: Bot ID
|
|
60
|
+
user_id: User ID
|
|
61
|
+
permission_level: Permission level (view, comment, edit, admin, owner)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if successful, False otherwise
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/user/{user_id}"
|
|
68
|
+
data = {"level": permission_level}
|
|
69
|
+
response = self.session.put(url, json=data)
|
|
70
|
+
return response.status_code == 200
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"Error setting user permission: {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def set_org_permission(
|
|
76
|
+
self, bot_id: str, org_id: str, permission_level: str
|
|
77
|
+
) -> bool:
|
|
78
|
+
"""Set permissions for an organization.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
bot_id: Bot ID
|
|
82
|
+
org_id: Organization ID
|
|
83
|
+
permission_level: Permission level (view, comment, edit, admin, owner)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if successful, False otherwise
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/org/{org_id}"
|
|
90
|
+
data = {"level": permission_level}
|
|
91
|
+
response = self.session.put(url, json=data)
|
|
92
|
+
return response.status_code == 200
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Error setting org permission: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def remove_user_permission(self, bot_id: str, user_id: str) -> bool:
|
|
98
|
+
"""Remove permissions for a specific user.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
bot_id: Bot ID
|
|
102
|
+
user_id: User ID
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if successful, False otherwise
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/user/{user_id}"
|
|
109
|
+
response = self.session.delete(url)
|
|
110
|
+
return response.status_code == 200
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Error removing user permission: {e}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def remove_org_permission(self, bot_id: str, org_id: str) -> bool:
|
|
116
|
+
"""Remove permissions for an organization.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
bot_id: Bot ID
|
|
120
|
+
org_id: Organization ID
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if successful, False otherwise
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/org/{org_id}"
|
|
127
|
+
response = self.session.delete(url)
|
|
128
|
+
return response.status_code == 200
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"Error removing org permission: {e}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def get_permissions(self, bot_id: str) -> Optional[Dict[str, Any]]:
|
|
134
|
+
"""Get all permissions for a bot.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
bot_id: Bot ID
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary with permission details or None if error
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
url = f"{self.base_url}/rbac/permissions/bot/{bot_id}"
|
|
144
|
+
response = self.session.get(url)
|
|
145
|
+
if response.status_code == 200:
|
|
146
|
+
return response.json()
|
|
147
|
+
return None
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Error getting permissions: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def check_access(self, bot_id: str, permission_level: str = "view") -> bool:
|
|
153
|
+
"""Check if current user has access to a bot.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
bot_id: Bot ID
|
|
157
|
+
permission_level: Required permission level
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if user has access, False otherwise
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
url = f"{self.base_url}/rbac/access/bot/{bot_id}/{permission_level}"
|
|
164
|
+
response = self.session.get(url)
|
|
165
|
+
if response.status_code == 200:
|
|
166
|
+
data = response.json()
|
|
167
|
+
return data.get("hasAccess", False)
|
|
168
|
+
return False
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f"Error checking access: {e}")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
def invite_user(self, bot_id: str, email: str, permission_level: str) -> bool:
|
|
174
|
+
"""Invite a user to access a bot.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
bot_id: Bot ID
|
|
178
|
+
email: User's email address
|
|
179
|
+
permission_level: Permission level to grant
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
True if successful, False otherwise
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
url = f"{self.base_url}/rbac/resource/bot/{bot_id}/invite"
|
|
186
|
+
data = {"email": email, "permissionLevel": permission_level}
|
|
187
|
+
response = self.session.post(url, json=data)
|
|
188
|
+
return response.status_code == 200
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f"Error inviting user: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Convenience functions for direct use
|
|
195
|
+
def set_bot_public(
|
|
196
|
+
bot_id: str, is_public: bool = True, permission_level: str = "view"
|
|
197
|
+
) -> bool:
|
|
198
|
+
"""Set public access for a bot.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
bot_id: Bot ID
|
|
202
|
+
is_public: Whether to make the bot public
|
|
203
|
+
permission_level: Permission level for public access
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
True if successful, False otherwise
|
|
207
|
+
"""
|
|
208
|
+
permissions = BotPermissions()
|
|
209
|
+
return permissions.set_public_access(bot_id, is_public, permission_level)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def set_bot_user_permission(bot_id: str, user_id: str, permission_level: str) -> bool:
|
|
213
|
+
"""Set permissions for a user on a bot.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
bot_id: Bot ID
|
|
217
|
+
user_id: User ID
|
|
218
|
+
permission_level: Permission level
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if successful, False otherwise
|
|
222
|
+
"""
|
|
223
|
+
permissions = BotPermissions()
|
|
224
|
+
return permissions.set_user_permission(bot_id, user_id, permission_level)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def set_bot_org_permission(bot_id: str, org_id: str, permission_level: str) -> bool:
|
|
228
|
+
"""Set permissions for an organization on a bot.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
bot_id: Bot ID
|
|
232
|
+
org_id: Organization ID
|
|
233
|
+
permission_level: Permission level
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
True if successful, False otherwise
|
|
237
|
+
"""
|
|
238
|
+
permissions = BotPermissions()
|
|
239
|
+
return permissions.set_org_permission(bot_id, org_id, permission_level)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def get_bot_permissions(bot_id: str) -> Optional[Dict[str, Any]]:
|
|
243
|
+
"""Get all permissions for a bot.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
bot_id: Bot ID
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dictionary with permission details or None if error
|
|
250
|
+
"""
|
|
251
|
+
permissions = BotPermissions()
|
|
252
|
+
return permissions.get_permissions(bot_id)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def check_bot_access(bot_id: str, permission_level: str = "view") -> bool:
|
|
256
|
+
"""Check if current user has access to a bot.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
bot_id: Bot ID
|
|
260
|
+
permission_level: Required permission level
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
True if user has access, False otherwise
|
|
264
|
+
"""
|
|
265
|
+
permissions = BotPermissions()
|
|
266
|
+
return permissions.check_access(bot_id, permission_level)
|
erdo/config/__init__.py
ADDED
erdo/config/config.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Configuration management for Erdo SDK."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config:
|
|
10
|
+
"""Configuration manager for Erdo SDK."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self, endpoint: Optional[str] = None, auth_token: Optional[str] = None
|
|
14
|
+
):
|
|
15
|
+
"""Initialize configuration.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
endpoint: API endpoint URL. If not provided, will try environment variable or config file.
|
|
19
|
+
auth_token: Authentication token. If not provided, will try environment variable or config file.
|
|
20
|
+
"""
|
|
21
|
+
self._endpoint = endpoint
|
|
22
|
+
self._auth_token = auth_token
|
|
23
|
+
self._config_yaml = Path.home() / ".erdo" / "config.yaml"
|
|
24
|
+
self._config_json = Path.home() / ".erdo" / "config.json"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def endpoint(self) -> str:
|
|
28
|
+
"""Get the API endpoint."""
|
|
29
|
+
if self._endpoint:
|
|
30
|
+
return self._endpoint
|
|
31
|
+
|
|
32
|
+
# Try environment variable
|
|
33
|
+
env_endpoint = os.environ.get("ERDO_ENDPOINT")
|
|
34
|
+
if env_endpoint:
|
|
35
|
+
return env_endpoint
|
|
36
|
+
|
|
37
|
+
# Try config file
|
|
38
|
+
config = self._load_config_file()
|
|
39
|
+
if config and "endpoint" in config:
|
|
40
|
+
return config["endpoint"]
|
|
41
|
+
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"No endpoint configured. Set ERDO_ENDPOINT environment variable or run 'erdo configure'"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def auth_token(self) -> str:
|
|
48
|
+
"""Get the authentication token."""
|
|
49
|
+
if self._auth_token:
|
|
50
|
+
return self._auth_token
|
|
51
|
+
|
|
52
|
+
# Try environment variable
|
|
53
|
+
env_token = os.environ.get("ERDO_AUTH_TOKEN")
|
|
54
|
+
if env_token:
|
|
55
|
+
return env_token
|
|
56
|
+
|
|
57
|
+
# Try config file
|
|
58
|
+
config = self._load_config_file()
|
|
59
|
+
if config and "auth_token" in config:
|
|
60
|
+
return config["auth_token"]
|
|
61
|
+
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"No auth token configured. Set ERDO_AUTH_TOKEN environment variable or run 'erdo login'"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _load_config_file(self) -> Optional[dict]:
|
|
67
|
+
"""Load configuration from file (supports both YAML and JSON)."""
|
|
68
|
+
# Try YAML first (CLI default format)
|
|
69
|
+
if self._config_yaml.exists():
|
|
70
|
+
try:
|
|
71
|
+
import yaml
|
|
72
|
+
|
|
73
|
+
with open(self._config_yaml, "r") as f:
|
|
74
|
+
return yaml.safe_load(f)
|
|
75
|
+
except:
|
|
76
|
+
try:
|
|
77
|
+
# Fallback to simple parsing if yaml not available
|
|
78
|
+
with open(self._config_yaml, "r") as f:
|
|
79
|
+
config = {}
|
|
80
|
+
for line in f:
|
|
81
|
+
if ": " in line:
|
|
82
|
+
key, value = line.strip().split(": ", 1)
|
|
83
|
+
config[key] = value
|
|
84
|
+
return config
|
|
85
|
+
except:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Try JSON as fallback
|
|
89
|
+
if self._config_json.exists():
|
|
90
|
+
try:
|
|
91
|
+
with open(self._config_json, "r") as f:
|
|
92
|
+
return json.load(f)
|
|
93
|
+
except (json.JSONDecodeError, IOError):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def save(self):
|
|
99
|
+
"""Save configuration to file (as JSON)."""
|
|
100
|
+
self._config_json.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
|
|
102
|
+
config = {}
|
|
103
|
+
if self._endpoint:
|
|
104
|
+
config["endpoint"] = self._endpoint
|
|
105
|
+
if self._auth_token:
|
|
106
|
+
config["auth_token"] = self._auth_token
|
|
107
|
+
|
|
108
|
+
with open(self._config_json, "w") as f:
|
|
109
|
+
json.dump(config, f, indent=2)
|
|
110
|
+
|
|
111
|
+
def set_endpoint(self, endpoint: str):
|
|
112
|
+
"""Set the API endpoint."""
|
|
113
|
+
self._endpoint = endpoint
|
|
114
|
+
|
|
115
|
+
def set_auth_token(self, auth_token: str):
|
|
116
|
+
"""Set the authentication token."""
|
|
117
|
+
self._auth_token = auth_token
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Global config instance
|
|
121
|
+
_config: Optional[Config] = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_config() -> Config:
|
|
125
|
+
"""Get the global configuration instance."""
|
|
126
|
+
global _config
|
|
127
|
+
if _config is None:
|
|
128
|
+
_config = Config()
|
|
129
|
+
return _config
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def set_config(endpoint: Optional[str] = None, auth_token: Optional[str] = None):
|
|
133
|
+
"""Set the global configuration.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
endpoint: API endpoint URL
|
|
137
|
+
auth_token: Authentication token
|
|
138
|
+
"""
|
|
139
|
+
global _config
|
|
140
|
+
_config = Config(endpoint=endpoint, auth_token=auth_token)
|
erdo/invoke/__init__.py
ADDED
erdo/invoke/client.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""API client for invoking bots via the backend orchestrator."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Generator, Optional, Union
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from ..config import get_config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SSEClient:
|
|
12
|
+
"""Client for Server-Sent Events streaming."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, response: requests.Response):
|
|
15
|
+
"""Initialize SSE client with a response object."""
|
|
16
|
+
self.response = response
|
|
17
|
+
self.response.encoding = "utf-8"
|
|
18
|
+
|
|
19
|
+
def events(self) -> Generator[Dict[str, Any], None, None]:
|
|
20
|
+
"""Yield events from the SSE stream."""
|
|
21
|
+
for line in self.response.iter_lines():
|
|
22
|
+
if line:
|
|
23
|
+
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
|
24
|
+
if line.startswith("data: "):
|
|
25
|
+
data = line[6:] # Remove 'data: ' prefix
|
|
26
|
+
if data.strip():
|
|
27
|
+
try:
|
|
28
|
+
yield json.loads(data)
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
# Some events might not be JSON
|
|
31
|
+
yield {"raw": data}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvokeClient:
|
|
35
|
+
"""Client for invoking bots via the Erdo backend."""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self, endpoint: Optional[str] = None, auth_token: Optional[str] = None
|
|
39
|
+
):
|
|
40
|
+
"""Initialize the invoke client.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
44
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
45
|
+
"""
|
|
46
|
+
config = get_config()
|
|
47
|
+
self.endpoint = endpoint or config.endpoint
|
|
48
|
+
self.auth_token = auth_token or config.auth_token
|
|
49
|
+
|
|
50
|
+
def invoke_bot(
|
|
51
|
+
self,
|
|
52
|
+
bot_identifier: str,
|
|
53
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
54
|
+
dataset_ids: Optional[list] = None,
|
|
55
|
+
stream: bool = False,
|
|
56
|
+
) -> Union[SSEClient, Dict[str, Any]]:
|
|
57
|
+
"""Invoke a bot via the backend orchestrator.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
bot_identifier: Bot ID or key (e.g., "erdo.data-analyzer")
|
|
61
|
+
parameters: Parameters to pass to the bot
|
|
62
|
+
dataset_ids: Optional dataset IDs to include
|
|
63
|
+
stream: Whether to return SSE client for streaming (default: False)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
SSEClient for streaming or final result dict for non-streaming
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
requests.RequestException: If the API request fails.
|
|
70
|
+
"""
|
|
71
|
+
url = f"{self.endpoint}/bots/{bot_identifier}/invoke"
|
|
72
|
+
headers = {
|
|
73
|
+
"Authorization": f"Bearer {self.auth_token}",
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
"Accept": "text/event-stream", # Endpoint always returns SSE
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Build invoke parameters
|
|
79
|
+
invoke_params = {
|
|
80
|
+
"parameters": parameters or {},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if dataset_ids:
|
|
84
|
+
invoke_params["dataset_ids"] = dataset_ids
|
|
85
|
+
|
|
86
|
+
# Make the request - always stream to handle SSE
|
|
87
|
+
response = requests.post(url, json=invoke_params, headers=headers, stream=True)
|
|
88
|
+
|
|
89
|
+
if response.status_code != 200:
|
|
90
|
+
error_msg = f"API request failed with status {response.status_code}"
|
|
91
|
+
try:
|
|
92
|
+
error_details = response.text
|
|
93
|
+
error_msg = f"{error_msg}: {error_details}"
|
|
94
|
+
except:
|
|
95
|
+
pass
|
|
96
|
+
raise requests.RequestException(error_msg)
|
|
97
|
+
|
|
98
|
+
sse_client = SSEClient(response)
|
|
99
|
+
|
|
100
|
+
if stream:
|
|
101
|
+
# Return SSE client for streaming
|
|
102
|
+
return sse_client
|
|
103
|
+
else:
|
|
104
|
+
# Consume all events and return final result
|
|
105
|
+
events = []
|
|
106
|
+
final_result = {}
|
|
107
|
+
|
|
108
|
+
for event in sse_client.events():
|
|
109
|
+
events.append(event)
|
|
110
|
+
|
|
111
|
+
# Look for completion or result events
|
|
112
|
+
if "payload" in event:
|
|
113
|
+
payload = event["payload"]
|
|
114
|
+
if isinstance(payload, dict):
|
|
115
|
+
if "result" in payload:
|
|
116
|
+
final_result = payload["result"]
|
|
117
|
+
elif "invocation_id" in payload:
|
|
118
|
+
final_result["invocation_id"] = payload["invocation_id"]
|
|
119
|
+
elif "bot_name" in payload:
|
|
120
|
+
final_result["bot_name"] = payload["bot_name"]
|
|
121
|
+
|
|
122
|
+
# Check for invocation completed
|
|
123
|
+
if event.get("type") == "invocation_completed":
|
|
124
|
+
if "result" in event:
|
|
125
|
+
final_result = event["result"]
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
# Return collected data
|
|
129
|
+
return {
|
|
130
|
+
"events": events,
|
|
131
|
+
"result": final_result,
|
|
132
|
+
"event_count": len(events),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def invoke_bot_from_thread(
|
|
136
|
+
self,
|
|
137
|
+
bot_identifier: str,
|
|
138
|
+
thread_id: str,
|
|
139
|
+
message: str,
|
|
140
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
141
|
+
stream: bool = False,
|
|
142
|
+
) -> Union[SSEClient, Dict[str, Any]]:
|
|
143
|
+
"""Invoke a bot from within a thread context.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
bot_identifier: Bot ID or key
|
|
147
|
+
thread_id: Thread ID to invoke from
|
|
148
|
+
message: User message
|
|
149
|
+
parameters: Additional parameters
|
|
150
|
+
stream: Whether to return SSE client for streaming (default: False)
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
SSEClient for streaming or final result dict
|
|
154
|
+
"""
|
|
155
|
+
url = f"{self.endpoint}/bots/{bot_identifier}/invoke-from-thread"
|
|
156
|
+
headers = {
|
|
157
|
+
"Authorization": f"Bearer {self.auth_token}",
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
"Accept": "text/event-stream", # Endpoint always returns SSE
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
invoke_params = {
|
|
163
|
+
"thread_id": thread_id,
|
|
164
|
+
"message": message,
|
|
165
|
+
"parameters": parameters or {},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Make the request - always stream to handle SSE
|
|
169
|
+
response = requests.post(url, json=invoke_params, headers=headers, stream=True)
|
|
170
|
+
|
|
171
|
+
if response.status_code != 200:
|
|
172
|
+
error_msg = f"API request failed with status {response.status_code}"
|
|
173
|
+
try:
|
|
174
|
+
error_details = response.text
|
|
175
|
+
error_msg = f"{error_msg}: {error_details}"
|
|
176
|
+
except:
|
|
177
|
+
pass
|
|
178
|
+
raise requests.RequestException(error_msg)
|
|
179
|
+
|
|
180
|
+
sse_client = SSEClient(response)
|
|
181
|
+
|
|
182
|
+
if stream:
|
|
183
|
+
# Return SSE client for streaming
|
|
184
|
+
return sse_client
|
|
185
|
+
else:
|
|
186
|
+
# Consume all events and return final result
|
|
187
|
+
events = []
|
|
188
|
+
final_result = {}
|
|
189
|
+
|
|
190
|
+
for event in sse_client.events():
|
|
191
|
+
events.append(event)
|
|
192
|
+
|
|
193
|
+
# Look for completion or result events
|
|
194
|
+
if "payload" in event:
|
|
195
|
+
payload = event["payload"]
|
|
196
|
+
if isinstance(payload, dict):
|
|
197
|
+
if "result" in payload:
|
|
198
|
+
final_result = payload["result"]
|
|
199
|
+
elif "invocation_id" in payload:
|
|
200
|
+
final_result["invocation_id"] = payload["invocation_id"]
|
|
201
|
+
|
|
202
|
+
# Check for invocation completed
|
|
203
|
+
if event.get("type") == "invocation_completed":
|
|
204
|
+
if "result" in event:
|
|
205
|
+
final_result = event["result"]
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
# Return collected data
|
|
209
|
+
return {
|
|
210
|
+
"events": events,
|
|
211
|
+
"result": final_result,
|
|
212
|
+
"event_count": len(events),
|
|
213
|
+
}
|