asteroid-odyssey 1.1.0__py3-none-any.whl → 1.2.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.
- asteroid_odyssey/__init__.py +10 -2
- asteroid_odyssey/agents_v1_gen/__init__.py +87 -0
- asteroid_odyssey/agents_v1_gen/api/__init__.py +7 -0
- asteroid_odyssey/agents_v1_gen/api/agent_profile_api.py +1696 -0
- asteroid_odyssey/{openapi_client → agents_v1_gen}/api/api_api.py +4 -4
- asteroid_odyssey/{openapi_client → agents_v1_gen}/api/execution_api.py +10 -10
- asteroid_odyssey/{openapi_client → agents_v1_gen}/api_client.py +6 -6
- asteroid_odyssey/{openapi_client → agents_v1_gen}/configuration.py +2 -2
- asteroid_odyssey/agents_v1_gen/models/__init__.py +34 -0
- asteroid_odyssey/agents_v1_gen/models/agent_profile.py +118 -0
- asteroid_odyssey/agents_v1_gen/models/country_code.py +44 -0
- asteroid_odyssey/agents_v1_gen/models/create_agent_profile_request.py +112 -0
- asteroid_odyssey/agents_v1_gen/models/credential.py +95 -0
- asteroid_odyssey/agents_v1_gen/models/delete_agent_profile200_response.py +87 -0
- asteroid_odyssey/{openapi_client → agents_v1_gen}/models/execution_result_response.py +2 -2
- asteroid_odyssey/{openapi_client → agents_v1_gen}/models/execution_status_response.py +1 -1
- asteroid_odyssey/agents_v1_gen/models/proxy_type.py +37 -0
- asteroid_odyssey/agents_v1_gen/models/update_agent_profile_request.py +112 -0
- asteroid_odyssey/{openapi_client → agents_v1_gen}/rest.py +1 -1
- asteroid_odyssey/agents_v2_gen/__init__.py +99 -0
- asteroid_odyssey/agents_v2_gen/api/__init__.py +5 -0
- asteroid_odyssey/agents_v2_gen/api/execution_api.py +625 -0
- asteroid_odyssey/agents_v2_gen/api_client.py +801 -0
- asteroid_odyssey/agents_v2_gen/api_response.py +21 -0
- asteroid_odyssey/agents_v2_gen/configuration.py +612 -0
- asteroid_odyssey/agents_v2_gen/exceptions.py +216 -0
- asteroid_odyssey/agents_v2_gen/models/__init__.py +42 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_completed.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_failed.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_started.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_generic.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_status_changed.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_step_completed.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_step_started.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_terminal.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_transitioned_node.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/activity_payload_union_user_message_received.py +100 -0
- asteroid_odyssey/agents_v2_gen/models/error.py +89 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity.py +98 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_action_completed_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_action_failed_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_action_started_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_generic_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_payload_union.py +252 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_status_changed_payload.py +88 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_step_completed_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_step_started_payload.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_transitioned_node_payload.py +89 -0
- asteroid_odyssey/agents_v2_gen/models/execution_activity_user_message_received_payload.py +89 -0
- asteroid_odyssey/agents_v2_gen/models/execution_status.py +43 -0
- asteroid_odyssey/agents_v2_gen/models/execution_terminal_payload.py +96 -0
- asteroid_odyssey/agents_v2_gen/models/execution_user_messages_add_text_body.py +87 -0
- asteroid_odyssey/agents_v2_gen/models/versions.py +37 -0
- asteroid_odyssey/agents_v2_gen/py.typed +0 -0
- asteroid_odyssey/agents_v2_gen/rest.py +258 -0
- asteroid_odyssey/client.py +635 -123
- {asteroid_odyssey-1.1.0.dist-info → asteroid_odyssey-1.2.0.dist-info}/METADATA +2 -1
- asteroid_odyssey-1.2.0.dist-info/RECORD +72 -0
- asteroid_odyssey/openapi_client/__init__.py +0 -71
- asteroid_odyssey/openapi_client/api/__init__.py +0 -6
- asteroid_odyssey/openapi_client/models/__init__.py +0 -27
- asteroid_odyssey-1.1.0.dist-info/RECORD +0 -28
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/api_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/exceptions.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/browser_session_recording_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/error_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/execution_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/execution_result.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/health_check200_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/health_check500_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/status.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/structured_agent_execution_request.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/models/upload_execution_files200_response.py +0 -0
- /asteroid_odyssey/{openapi_client → agents_v1_gen}/py.typed +0 -0
- {asteroid_odyssey-1.1.0.dist-info → asteroid_odyssey-1.2.0.dist-info}/WHEEL +0 -0
- {asteroid_odyssey-1.1.0.dist-info → asteroid_odyssey-1.2.0.dist-info}/top_level.txt +0 -0
asteroid_odyssey/client.py
CHANGED
|
@@ -11,69 +11,149 @@ without modifying any generated files.
|
|
|
11
11
|
import time
|
|
12
12
|
import os
|
|
13
13
|
import logging
|
|
14
|
+
import base64
|
|
14
15
|
from typing import Dict, Any, Optional, List, Union, Tuple
|
|
15
|
-
from .
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
from cryptography.hazmat.primitives import serialization
|
|
17
|
+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
18
|
+
from .agents_v1_gen import (
|
|
19
|
+
Configuration as AgentsV1Configuration,
|
|
20
|
+
ApiClient as AgentsV1ApiClient,
|
|
21
|
+
APIApi as AgentsV1APIApi,
|
|
22
|
+
ExecutionApi as AgentsV1ExecutionApi,
|
|
23
|
+
AgentProfileApi as AgentsV1AgentProfileApi,
|
|
20
24
|
ExecutionStatusResponse,
|
|
21
|
-
|
|
22
|
-
BrowserSessionRecordingResponse,
|
|
25
|
+
ExecutionResult,
|
|
23
26
|
UploadExecutionFiles200Response,
|
|
24
27
|
Status,
|
|
25
|
-
StructuredAgentExecutionRequest
|
|
28
|
+
StructuredAgentExecutionRequest,
|
|
29
|
+
CreateAgentProfileRequest,
|
|
30
|
+
UpdateAgentProfileRequest,
|
|
31
|
+
DeleteAgentProfile200Response,
|
|
32
|
+
AgentProfile,
|
|
33
|
+
Credential,
|
|
26
34
|
)
|
|
27
|
-
from .
|
|
35
|
+
from .agents_v1_gen.exceptions import ApiException
|
|
36
|
+
from .agents_v2_gen import (
|
|
37
|
+
Configuration as AgentsV2Configuration,
|
|
38
|
+
ApiClient as AgentsV2ApiClient,
|
|
39
|
+
ExecutionApi as AgentsV2ExecutionApi,
|
|
40
|
+
ExecutionActivity,
|
|
41
|
+
ExecutionUserMessagesAddTextBody,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AsteroidAPIError(Exception):
|
|
46
|
+
"""Base exception for all Asteroid API related errors."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ExecutionError(AsteroidAPIError):
|
|
51
|
+
"""Raised when an execution fails or is cancelled."""
|
|
52
|
+
def __init__(self, message: str, execution_result: Optional[ExecutionResult] = None):
|
|
53
|
+
super().__init__(message)
|
|
54
|
+
self.execution_result = execution_result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TimeoutError(AsteroidAPIError):
|
|
58
|
+
"""Raised when an execution times out."""
|
|
59
|
+
def __init__(self, message: str):
|
|
60
|
+
super().__init__(message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def encrypt_with_public_key(plaintext: str, pem_public_key: str) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Encrypt plaintext using RSA public key with PKCS1v15 padding.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
plaintext: The string to encrypt
|
|
69
|
+
pem_public_key: PEM-formatted RSA public key
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Base64-encoded encrypted string
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If encryption fails or key is invalid
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
encrypted = encrypt_with_public_key("my_password", public_key_pem)
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
# Load the PEM public key (matches node-forge behavior)
|
|
82
|
+
public_key = serialization.load_pem_public_key(pem_public_key.encode('utf-8'))
|
|
83
|
+
|
|
84
|
+
if not isinstance(public_key, rsa.RSAPublicKey):
|
|
85
|
+
raise ValueError("Invalid RSA public key")
|
|
86
|
+
|
|
87
|
+
# Encrypt using PKCS1v15 padding (matches "RSAES-PKCS1-V1_5" from TypeScript)
|
|
88
|
+
encrypted_bytes = public_key.encrypt(
|
|
89
|
+
plaintext.encode('utf-8'),
|
|
90
|
+
padding.PKCS1v15()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Encode as base64 (matches forge.util.encode64)
|
|
94
|
+
return base64.b64encode(encrypted_bytes).decode('utf-8')
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise ValueError(f"Failed to encrypt: {str(e)}") from e
|
|
28
98
|
|
|
29
99
|
|
|
30
100
|
class AsteroidClient:
|
|
31
101
|
"""
|
|
32
102
|
High-level client for the Asteroid Agents API.
|
|
33
|
-
|
|
103
|
+
|
|
34
104
|
This class provides a convenient interface for executing agents and managing
|
|
35
105
|
their execution lifecycle, similar to the TypeScript SDK.
|
|
36
106
|
"""
|
|
37
|
-
|
|
107
|
+
|
|
38
108
|
def __init__(self, api_key: str, base_url: Optional[str] = None):
|
|
39
109
|
"""
|
|
40
110
|
Create an API client with the provided API key.
|
|
41
|
-
|
|
111
|
+
|
|
42
112
|
Args:
|
|
43
113
|
api_key: Your API key for authentication
|
|
44
114
|
base_url: Optional base URL (defaults to https://odyssey.asteroid.ai/api/v1)
|
|
45
|
-
|
|
115
|
+
|
|
46
116
|
Example:
|
|
47
117
|
client = AsteroidClient('your-api-key')
|
|
48
118
|
"""
|
|
49
119
|
if api_key is None:
|
|
50
120
|
raise TypeError("API key cannot be None")
|
|
51
|
-
|
|
121
|
+
|
|
52
122
|
# Configure the API client
|
|
53
|
-
config =
|
|
123
|
+
config = AgentsV1Configuration(
|
|
54
124
|
host=base_url or "https://odyssey.asteroid.ai/api/v1",
|
|
55
125
|
api_key={'ApiKeyAuth': api_key}
|
|
56
126
|
)
|
|
57
|
-
|
|
58
|
-
self.api_client =
|
|
59
|
-
self.api_api =
|
|
60
|
-
self.execution_api =
|
|
61
|
-
|
|
62
|
-
|
|
127
|
+
|
|
128
|
+
self.api_client = AgentsV1ApiClient(config)
|
|
129
|
+
self.api_api = AgentsV1APIApi(self.api_client)
|
|
130
|
+
self.execution_api = AgentsV1ExecutionApi(self.api_client)
|
|
131
|
+
self.agent_profile_api = AgentsV1AgentProfileApi(self.api_client)
|
|
132
|
+
|
|
133
|
+
self.agents_v2_config = AgentsV2Configuration(
|
|
134
|
+
host=base_url or "https://odyssey.asteroid.ai/agents/v2",
|
|
135
|
+
api_key={'ApiKeyAuth': api_key}
|
|
136
|
+
)
|
|
137
|
+
self.agents_v2_api_client = AgentsV2ApiClient(self.agents_v2_config)
|
|
138
|
+
self.agents_v2_execution_api = AgentsV2ExecutionApi(self.agents_v2_api_client)
|
|
139
|
+
|
|
140
|
+
# --- V1 ---
|
|
141
|
+
|
|
142
|
+
def execute_agent(self, agent_id: str, execution_data: Dict[str, Any], agent_profile_id: Optional[str] = None) -> str:
|
|
63
143
|
"""
|
|
64
144
|
Execute an agent with the provided parameters.
|
|
65
|
-
|
|
145
|
+
|
|
66
146
|
Args:
|
|
67
147
|
agent_id: The ID of the agent to execute
|
|
68
148
|
execution_data: The execution parameters
|
|
69
149
|
agent_profile_id: Optional ID of the agent profile
|
|
70
|
-
|
|
150
|
+
|
|
71
151
|
Returns:
|
|
72
152
|
The execution ID
|
|
73
|
-
|
|
153
|
+
|
|
74
154
|
Raises:
|
|
75
|
-
|
|
76
|
-
|
|
155
|
+
AsteroidAPIError: If the execution request fails
|
|
156
|
+
|
|
77
157
|
Example:
|
|
78
158
|
execution_id = client.execute_agent('my-agent-id', {'input': 'some dynamic value'}, 'agent-profile-id')
|
|
79
159
|
"""
|
|
@@ -82,21 +162,21 @@ class AsteroidClient:
|
|
|
82
162
|
response = self.execution_api.execute_agent_structured(agent_id, req)
|
|
83
163
|
return response.execution_id
|
|
84
164
|
except ApiException as e:
|
|
85
|
-
raise
|
|
86
|
-
|
|
165
|
+
raise AsteroidAPIError(f"Failed to execute agent: {e}") from e
|
|
166
|
+
|
|
87
167
|
def get_execution_status(self, execution_id: str) -> ExecutionStatusResponse:
|
|
88
168
|
"""
|
|
89
169
|
Get the current status for an execution.
|
|
90
|
-
|
|
170
|
+
|
|
91
171
|
Args:
|
|
92
172
|
execution_id: The execution identifier
|
|
93
|
-
|
|
173
|
+
|
|
94
174
|
Returns:
|
|
95
175
|
The execution status details
|
|
96
|
-
|
|
176
|
+
|
|
97
177
|
Raises:
|
|
98
|
-
|
|
99
|
-
|
|
178
|
+
AsteroidAPIError: If the status request fails
|
|
179
|
+
|
|
100
180
|
Example:
|
|
101
181
|
status = client.get_execution_status(execution_id)
|
|
102
182
|
print(status.status)
|
|
@@ -104,89 +184,123 @@ class AsteroidClient:
|
|
|
104
184
|
try:
|
|
105
185
|
return self.execution_api.get_execution_status(execution_id)
|
|
106
186
|
except ApiException as e:
|
|
107
|
-
raise
|
|
108
|
-
|
|
109
|
-
def get_execution_result(self, execution_id: str) ->
|
|
187
|
+
raise AsteroidAPIError(f"Failed to get execution status: {e}") from e
|
|
188
|
+
|
|
189
|
+
def get_execution_result(self, execution_id: str) -> ExecutionResult:
|
|
110
190
|
"""
|
|
111
191
|
Get the final result of an execution.
|
|
112
|
-
|
|
192
|
+
|
|
113
193
|
Args:
|
|
114
194
|
execution_id: The execution identifier
|
|
115
|
-
|
|
195
|
+
|
|
116
196
|
Returns:
|
|
117
|
-
The result object
|
|
118
|
-
|
|
197
|
+
The execution result object
|
|
198
|
+
|
|
119
199
|
Raises:
|
|
120
|
-
|
|
121
|
-
|
|
200
|
+
AsteroidAPIError: If the result request fails or execution failed
|
|
201
|
+
|
|
122
202
|
Example:
|
|
123
203
|
result = client.get_execution_result(execution_id)
|
|
124
|
-
print(result)
|
|
204
|
+
print(result.outcome, result.reasoning)
|
|
125
205
|
"""
|
|
126
206
|
try:
|
|
127
207
|
response = self.execution_api.get_execution_result(execution_id)
|
|
128
|
-
|
|
208
|
+
|
|
129
209
|
if response.error:
|
|
130
|
-
raise
|
|
210
|
+
raise AsteroidAPIError(response.error)
|
|
211
|
+
|
|
212
|
+
# Handle case where execution_result might be None or have invalid data
|
|
213
|
+
if response.execution_result is None:
|
|
214
|
+
raise AsteroidAPIError("Execution result is not available yet")
|
|
131
215
|
|
|
132
|
-
return response.execution_result
|
|
216
|
+
return response.execution_result
|
|
133
217
|
except ApiException as e:
|
|
134
|
-
raise
|
|
135
|
-
|
|
218
|
+
raise AsteroidAPIError(f"Failed to get execution result: {e}") from e
|
|
219
|
+
except Exception as e:
|
|
220
|
+
# Handle validation errors from ExecutionResult model
|
|
221
|
+
if "must be one of enum values" in str(e):
|
|
222
|
+
raise AsteroidAPIError("Execution result is not available yet - execution may still be running") from e
|
|
223
|
+
raise e
|
|
224
|
+
|
|
136
225
|
def wait_for_execution_result(
|
|
137
|
-
self,
|
|
138
|
-
execution_id: str,
|
|
139
|
-
interval: float = 1.0,
|
|
226
|
+
self,
|
|
227
|
+
execution_id: str,
|
|
228
|
+
interval: float = 1.0,
|
|
140
229
|
timeout: float = 3600.0
|
|
141
|
-
) ->
|
|
230
|
+
) -> ExecutionResult:
|
|
142
231
|
"""
|
|
143
232
|
Wait for an execution to reach a terminal state and return the result.
|
|
144
|
-
|
|
145
|
-
Continuously polls the execution status until it's either "completed",
|
|
233
|
+
|
|
234
|
+
Continuously polls the execution status until it's either "completed",
|
|
146
235
|
"cancelled", or "failed".
|
|
147
|
-
|
|
236
|
+
|
|
148
237
|
Args:
|
|
149
238
|
execution_id: The execution identifier
|
|
150
239
|
interval: Polling interval in seconds (default is 1.0)
|
|
151
240
|
timeout: Maximum wait time in seconds (default is 3600 - 1 hour)
|
|
152
|
-
|
|
241
|
+
|
|
153
242
|
Returns:
|
|
154
|
-
The execution result
|
|
155
|
-
|
|
243
|
+
The execution result object
|
|
244
|
+
|
|
156
245
|
Raises:
|
|
157
|
-
|
|
158
|
-
|
|
246
|
+
ValueError: If interval or timeout parameters are invalid
|
|
247
|
+
TimeoutError: If the execution times out
|
|
248
|
+
ExecutionError: If the execution ends as "cancelled" or "failed"
|
|
249
|
+
|
|
159
250
|
Example:
|
|
160
251
|
result = client.wait_for_execution_result(execution_id, interval=2.0)
|
|
252
|
+
print(result.outcome, result.reasoning)
|
|
161
253
|
"""
|
|
254
|
+
# Validate input parameters
|
|
255
|
+
if interval <= 0:
|
|
256
|
+
raise ValueError("interval must be positive")
|
|
257
|
+
if timeout <= 0:
|
|
258
|
+
raise ValueError("timeout must be positive")
|
|
162
259
|
start_time = time.time()
|
|
163
|
-
|
|
260
|
+
|
|
164
261
|
while True:
|
|
165
262
|
elapsed_time = time.time() - start_time
|
|
166
263
|
if elapsed_time >= timeout:
|
|
167
|
-
raise
|
|
168
|
-
|
|
264
|
+
raise TimeoutError(f"Execution {execution_id} timed out after {timeout}s")
|
|
265
|
+
|
|
169
266
|
status_response = self.get_execution_status(execution_id)
|
|
170
267
|
current_status = status_response.status
|
|
171
|
-
|
|
268
|
+
|
|
172
269
|
if current_status == Status.COMPLETED:
|
|
173
|
-
|
|
270
|
+
try:
|
|
271
|
+
return self.get_execution_result(execution_id)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
if "not available yet" in str(e):
|
|
274
|
+
# Execution completed but result not ready yet, wait a bit more
|
|
275
|
+
time.sleep(interval)
|
|
276
|
+
continue
|
|
277
|
+
raise e
|
|
174
278
|
elif current_status in [Status.FAILED, Status.CANCELLED]:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
279
|
+
# Get the execution result to provide outcome and reasoning
|
|
280
|
+
try:
|
|
281
|
+
execution_result = self.get_execution_result(execution_id)
|
|
282
|
+
reason = f" - {status_response.reason}" if status_response.reason else ""
|
|
283
|
+
raise ExecutionError(
|
|
284
|
+
f"Execution {execution_id} ended with status: {current_status.value}{reason}",
|
|
285
|
+
execution_result
|
|
286
|
+
)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
# If we can't get the execution result, fall back to the original behavior
|
|
289
|
+
reason = f" - {status_response.reason}" if status_response.reason else ""
|
|
290
|
+
raise ExecutionError(f"Execution {execution_id} ended with status: {current_status.value}{reason}") from e
|
|
291
|
+
|
|
178
292
|
# Wait for the specified interval before polling again
|
|
179
293
|
time.sleep(interval)
|
|
180
|
-
|
|
294
|
+
|
|
181
295
|
def upload_execution_files(
|
|
182
|
-
self,
|
|
183
|
-
execution_id: str,
|
|
296
|
+
self,
|
|
297
|
+
execution_id: str,
|
|
184
298
|
files: List[Union[bytes, str, Tuple[str, bytes]]],
|
|
185
299
|
default_filename: str = "file.txt"
|
|
186
300
|
) -> UploadExecutionFiles200Response:
|
|
187
301
|
"""
|
|
188
302
|
Upload files to an execution.
|
|
189
|
-
|
|
303
|
+
|
|
190
304
|
Args:
|
|
191
305
|
execution_id: The execution identifier
|
|
192
306
|
files: List of files to upload. Each file can be:
|
|
@@ -194,25 +308,25 @@ class AsteroidClient:
|
|
|
194
308
|
- str: File path as string (will read file and use filename)
|
|
195
309
|
- Tuple[str, bytes]: (filename, file_content) tuple
|
|
196
310
|
default_filename: Default filename to use when file is provided as bytes
|
|
197
|
-
|
|
311
|
+
|
|
198
312
|
Returns:
|
|
199
313
|
The upload response containing message and file IDs
|
|
200
|
-
|
|
314
|
+
|
|
201
315
|
Raises:
|
|
202
316
|
Exception: If the upload request fails
|
|
203
|
-
|
|
317
|
+
|
|
204
318
|
Example:
|
|
205
319
|
# Upload with file content (file should be in your current working directory)
|
|
206
320
|
with open('hello.txt', 'r') as f:
|
|
207
321
|
file_content = f.read()
|
|
208
|
-
|
|
322
|
+
|
|
209
323
|
response = client.upload_execution_files(execution_id, [file_content.encode()])
|
|
210
324
|
print(f"Uploaded files: {response.file_ids}")
|
|
211
|
-
|
|
325
|
+
|
|
212
326
|
# Upload with filename and content
|
|
213
327
|
files = [('hello.txt', file_content.encode())]
|
|
214
328
|
response = client.upload_execution_files(execution_id, files)
|
|
215
|
-
|
|
329
|
+
|
|
216
330
|
# Or create content directly
|
|
217
331
|
hello_content = "Hello World!".encode()
|
|
218
332
|
response = client.upload_execution_files(execution_id, [hello_content])
|
|
@@ -246,25 +360,25 @@ class AsteroidClient:
|
|
|
246
360
|
# Other types - convert to string content and encode
|
|
247
361
|
content = str(file_item).encode()
|
|
248
362
|
processed_files.append((default_filename, content))
|
|
249
|
-
|
|
363
|
+
|
|
250
364
|
response = self.execution_api.upload_execution_files(execution_id, files=processed_files)
|
|
251
365
|
return response
|
|
252
366
|
except ApiException as e:
|
|
253
|
-
raise
|
|
254
|
-
|
|
367
|
+
raise AsteroidAPIError(f"Failed to upload execution files: {e}") from e
|
|
368
|
+
|
|
255
369
|
def get_browser_session_recording(self, execution_id: str) -> str:
|
|
256
370
|
"""
|
|
257
371
|
Get the browser session recording URL for a completed execution.
|
|
258
|
-
|
|
372
|
+
|
|
259
373
|
Args:
|
|
260
374
|
execution_id: The execution identifier
|
|
261
|
-
|
|
375
|
+
|
|
262
376
|
Returns:
|
|
263
377
|
The URL of the browser session recording
|
|
264
|
-
|
|
378
|
+
|
|
265
379
|
Raises:
|
|
266
380
|
Exception: If the recording request fails
|
|
267
|
-
|
|
381
|
+
|
|
268
382
|
Example:
|
|
269
383
|
recording_url = client.get_browser_session_recording(execution_id)
|
|
270
384
|
print(f"Recording available at: {recording_url}")
|
|
@@ -273,12 +387,199 @@ class AsteroidClient:
|
|
|
273
387
|
response = self.execution_api.get_browser_session_recording(execution_id)
|
|
274
388
|
return response.recording_url
|
|
275
389
|
except ApiException as e:
|
|
276
|
-
raise
|
|
277
|
-
|
|
390
|
+
raise AsteroidAPIError(f"Failed to get browser session recording: {e}") from e
|
|
391
|
+
|
|
392
|
+
def get_agent_profiles(self, organization_id: str) -> List[AgentProfile]:
|
|
393
|
+
"""
|
|
394
|
+
Get a list of agent profiles for a specific organization.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
organization_id: The organization identifier (required)
|
|
398
|
+
Returns:
|
|
399
|
+
A list of agent profiles
|
|
400
|
+
Raises:
|
|
401
|
+
Exception: If the agent profiles request fails
|
|
402
|
+
Example:
|
|
403
|
+
profiles = client.get_agent_profiles("org-123")
|
|
404
|
+
"""
|
|
405
|
+
try:
|
|
406
|
+
response = self.agent_profile_api.get_agent_profiles(organization_id=organization_id)
|
|
407
|
+
return response # response is already a List[AgentProfile]
|
|
408
|
+
except ApiException as e:
|
|
409
|
+
raise AsteroidAPIError(f"Failed to get agent profiles: {e}") from e
|
|
410
|
+
def get_agent_profile(self, profile_id: str) -> AgentProfile:
|
|
411
|
+
"""
|
|
412
|
+
Get an agent profile by ID.
|
|
413
|
+
Args:
|
|
414
|
+
profile_id: The ID of the agent profile
|
|
415
|
+
Returns:
|
|
416
|
+
The agent profile
|
|
417
|
+
Raises:
|
|
418
|
+
Exception: If the agent profile request fails
|
|
419
|
+
Example:
|
|
420
|
+
profile = client.get_agent_profile("profile_id")
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
response = self.agent_profile_api.get_agent_profile(profile_id)
|
|
424
|
+
return response
|
|
425
|
+
except ApiException as e:
|
|
426
|
+
raise AsteroidAPIError(f"Failed to get agent profile: {e}") from e
|
|
427
|
+
|
|
428
|
+
def create_agent_profile(self, request: CreateAgentProfileRequest) -> AgentProfile:
|
|
429
|
+
"""
|
|
430
|
+
Create an agent profile with automatic credential encryption.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
request: The request object
|
|
434
|
+
Returns:
|
|
435
|
+
The agent profile
|
|
436
|
+
Raises:
|
|
437
|
+
Exception: If the agent profile creation fails
|
|
438
|
+
Example:
|
|
439
|
+
request = CreateAgentProfileRequest(
|
|
440
|
+
name="My Agent Profile",
|
|
441
|
+
description="This is my agent profile",
|
|
442
|
+
organization_id="org-123",
|
|
443
|
+
proxy_cc=CountryCode.US,
|
|
444
|
+
proxy_type=ProxyType.RESIDENTIAL,
|
|
445
|
+
captcha_solver_active=True,
|
|
446
|
+
sticky_ip=True,
|
|
447
|
+
credentials=[Credential(name="user", data="password")]
|
|
448
|
+
)
|
|
449
|
+
profile = client.create_agent_profile(request)
|
|
450
|
+
"""
|
|
451
|
+
try:
|
|
452
|
+
# Create a copy to avoid modifying the original request
|
|
453
|
+
processed_request = request
|
|
454
|
+
|
|
455
|
+
# If credentials are provided, encrypt them before sending
|
|
456
|
+
if request.credentials and len(request.credentials) > 0:
|
|
457
|
+
# Get the public key for encryption
|
|
458
|
+
public_key = self.get_credentials_public_key()
|
|
459
|
+
|
|
460
|
+
# Encrypt each credential's data field
|
|
461
|
+
encrypted_credentials = []
|
|
462
|
+
for credential in request.credentials:
|
|
463
|
+
encrypted_credential = Credential(
|
|
464
|
+
name=credential.name,
|
|
465
|
+
data=encrypt_with_public_key(credential.data, public_key),
|
|
466
|
+
id=credential.id,
|
|
467
|
+
created_at=credential.created_at
|
|
468
|
+
)
|
|
469
|
+
encrypted_credentials.append(encrypted_credential)
|
|
470
|
+
|
|
471
|
+
# Create new request with encrypted credentials
|
|
472
|
+
processed_request = CreateAgentProfileRequest(
|
|
473
|
+
name=request.name,
|
|
474
|
+
description=request.description,
|
|
475
|
+
organization_id=request.organization_id,
|
|
476
|
+
proxy_cc=request.proxy_cc,
|
|
477
|
+
proxy_type=request.proxy_type,
|
|
478
|
+
captcha_solver_active=request.captcha_solver_active,
|
|
479
|
+
sticky_ip=request.sticky_ip,
|
|
480
|
+
credentials=encrypted_credentials
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
response = self.agent_profile_api.create_agent_profile(processed_request)
|
|
484
|
+
return response
|
|
485
|
+
except ApiException as e:
|
|
486
|
+
raise AsteroidAPIError(f"Failed to create agent profile: {e}") from e
|
|
487
|
+
def update_agent_profile(self, profile_id: str, request: UpdateAgentProfileRequest) -> AgentProfile:
|
|
488
|
+
"""
|
|
489
|
+
Update an agent profile with automatic credential encryption.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
profile_id: The ID of the agent profile
|
|
493
|
+
request: The request object
|
|
494
|
+
Returns:
|
|
495
|
+
The agent profile
|
|
496
|
+
Raises:
|
|
497
|
+
Exception: If the agent profile update fails
|
|
498
|
+
Example:
|
|
499
|
+
request = UpdateAgentProfileRequest(
|
|
500
|
+
name="My Agent Profile",
|
|
501
|
+
description="This is my agent profile",
|
|
502
|
+
credentials_to_add=[Credential(name="api_key", data="secret")]
|
|
503
|
+
)
|
|
504
|
+
profile = client.update_agent_profile("profile_id", request)
|
|
505
|
+
"""
|
|
506
|
+
try:
|
|
507
|
+
# Create a copy to avoid modifying the original request
|
|
508
|
+
processed_request = request
|
|
509
|
+
|
|
510
|
+
# If credentials_to_add are provided, encrypt them before sending
|
|
511
|
+
if request.credentials_to_add and len(request.credentials_to_add) > 0:
|
|
512
|
+
# Get the public key for encryption
|
|
513
|
+
public_key = self.get_credentials_public_key()
|
|
514
|
+
|
|
515
|
+
# Encrypt the data field of each credential to add
|
|
516
|
+
encrypted_credentials_to_add = []
|
|
517
|
+
for credential in request.credentials_to_add:
|
|
518
|
+
encrypted_credential = Credential(
|
|
519
|
+
name=credential.name,
|
|
520
|
+
data=encrypt_with_public_key(credential.data, public_key),
|
|
521
|
+
id=credential.id,
|
|
522
|
+
created_at=credential.created_at
|
|
523
|
+
)
|
|
524
|
+
encrypted_credentials_to_add.append(encrypted_credential)
|
|
525
|
+
|
|
526
|
+
# Create new request with encrypted credentials
|
|
527
|
+
processed_request = UpdateAgentProfileRequest(
|
|
528
|
+
name=request.name,
|
|
529
|
+
description=request.description,
|
|
530
|
+
proxy_cc=request.proxy_cc,
|
|
531
|
+
proxy_type=request.proxy_type,
|
|
532
|
+
captcha_solver_active=request.captcha_solver_active,
|
|
533
|
+
sticky_ip=request.sticky_ip,
|
|
534
|
+
credentials_to_add=encrypted_credentials_to_add,
|
|
535
|
+
credentials_to_delete=request.credentials_to_delete
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
response = self.agent_profile_api.update_agent_profile(profile_id, processed_request)
|
|
539
|
+
return response
|
|
540
|
+
except ApiException as e:
|
|
541
|
+
raise AsteroidAPIError(f"Failed to update agent profile: {e}") from e
|
|
542
|
+
def delete_agent_profile(self, profile_id: str) -> DeleteAgentProfile200Response:
|
|
543
|
+
"""
|
|
544
|
+
Delete an agent profile.
|
|
545
|
+
Args:
|
|
546
|
+
profile_id: The ID of the agent profile
|
|
547
|
+
Returns:
|
|
548
|
+
Confirmation message from the server
|
|
549
|
+
Raises:
|
|
550
|
+
Exception: If the agent profile deletion fails
|
|
551
|
+
Example:
|
|
552
|
+
response = client.delete_agent_profile("profile_id")
|
|
553
|
+
"""
|
|
554
|
+
try:
|
|
555
|
+
response = self.agent_profile_api.delete_agent_profile(profile_id)
|
|
556
|
+
return response
|
|
557
|
+
except ApiException as e:
|
|
558
|
+
raise AsteroidAPIError(f"Failed to delete agent profile: {e}") from e
|
|
559
|
+
|
|
560
|
+
def get_credentials_public_key(self) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Get the public key for encrypting credentials.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
PEM-formatted RSA public key string
|
|
566
|
+
|
|
567
|
+
Raises:
|
|
568
|
+
Exception: If the public key request fails
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
public_key = client.get_credentials_public_key()
|
|
572
|
+
"""
|
|
573
|
+
try:
|
|
574
|
+
response = self.agent_profile_api.get_credentials_public_key()
|
|
575
|
+
return response
|
|
576
|
+
except ApiException as e:
|
|
577
|
+
raise AsteroidAPIError(f"Failed to get credentials public key: {e}") from e
|
|
578
|
+
|
|
278
579
|
def __enter__(self):
|
|
279
580
|
"""Context manager entry."""
|
|
280
581
|
return self
|
|
281
|
-
|
|
582
|
+
|
|
282
583
|
def __exit__(self, exc_type, exc_value, tb):
|
|
283
584
|
"""Context manager exit: clean up API client connection pool."""
|
|
284
585
|
try:
|
|
@@ -297,39 +598,98 @@ class AsteroidClient:
|
|
|
297
598
|
# Returning False allows any exception in the 'with' block to propagate
|
|
298
599
|
return False
|
|
299
600
|
|
|
601
|
+
# Utility methods for nicer response formatting
|
|
602
|
+
def format_agent_profile(self, profile: AgentProfile) -> str:
|
|
603
|
+
"""Format an agent profile for nice display."""
|
|
604
|
+
credentials_info = f"{[(credential.name, credential.id) for credential in profile.credentials]}" if profile.credentials else "No credentials"
|
|
605
|
+
return f"""Agent Profile:
|
|
606
|
+
ID: {profile.id}
|
|
607
|
+
Name: {profile.name}
|
|
608
|
+
Description: {profile.description}
|
|
609
|
+
Organization: {profile.organization_id}
|
|
610
|
+
Proxy: {profile.proxy_cc.value} ({profile.proxy_type.value})
|
|
611
|
+
Captcha Solver: {'Enabled' if profile.captcha_solver_active else 'Disabled'}
|
|
612
|
+
Sticky IP: {'Yes' if profile.sticky_ip else 'No'}
|
|
613
|
+
Credentials: {credentials_info}
|
|
614
|
+
Created: {profile.created_at.strftime('%Y-%m-%d %H:%M:%S')}
|
|
615
|
+
Updated: {profile.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"""
|
|
616
|
+
|
|
617
|
+
def format_agent_profiles_list(self, profiles: List[AgentProfile]) -> str:
|
|
618
|
+
"""Format a list of agent profiles for nice display."""
|
|
619
|
+
if not profiles:
|
|
620
|
+
return "No agent profiles found."
|
|
621
|
+
|
|
622
|
+
result = f"Found {len(profiles)} agent profile(s):\n"
|
|
623
|
+
for i, profile in enumerate(profiles, 1):
|
|
624
|
+
credentials_count = len(profile.credentials) if profile.credentials else 0
|
|
625
|
+
result += f" {i}. {profile.name} (ID: {profile.id[:8]}...) - {credentials_count} credentials\n"
|
|
626
|
+
return result.rstrip()
|
|
627
|
+
|
|
628
|
+
# --- V2 ---
|
|
629
|
+
|
|
630
|
+
def get_last_n_execution_activities(self, execution_id: str, n: int) -> List[ExecutionActivity]:
|
|
631
|
+
"""
|
|
632
|
+
Get the last N execution activities for a given execution ID, sorted by their timestamp in descending order.
|
|
633
|
+
Args:
|
|
634
|
+
execution_id: The execution identifier
|
|
635
|
+
n: The number of activities to return
|
|
636
|
+
Returns:
|
|
637
|
+
A list of execution activities
|
|
638
|
+
Raises:
|
|
639
|
+
Exception: If the execution activities request fails
|
|
640
|
+
"""
|
|
641
|
+
return self.agents_v2_execution_api.activities_get(execution_id, order="desc", limit=n)
|
|
642
|
+
def add_message_to_execution(self, execution_id: str, message: str) -> None:
|
|
643
|
+
"""
|
|
644
|
+
Add a message to an execution.
|
|
645
|
+
Args:
|
|
646
|
+
execution_id: The execution identifier
|
|
647
|
+
message: The message to add
|
|
648
|
+
Returns:
|
|
649
|
+
None
|
|
650
|
+
Raises:
|
|
651
|
+
Exception: If the message addition fails
|
|
652
|
+
Example:
|
|
653
|
+
add_message_to_execution(client, "execution_id", "Hello, world!")
|
|
654
|
+
"""
|
|
655
|
+
message_body = ExecutionUserMessagesAddTextBody(message=message)
|
|
656
|
+
return self.agents_v2_execution_api.user_messages_add(execution_id, message_body)
|
|
657
|
+
|
|
300
658
|
|
|
301
659
|
# Convenience functions that mirror the TypeScript SDK pattern
|
|
302
660
|
def create_client(api_key: str, base_url: Optional[str] = None) -> AsteroidClient:
|
|
303
661
|
"""
|
|
304
662
|
Create an API client with a provided API key.
|
|
305
|
-
|
|
663
|
+
|
|
306
664
|
This is a convenience function that creates an AsteroidClient instance.
|
|
307
|
-
|
|
665
|
+
|
|
308
666
|
Args:
|
|
309
667
|
api_key: Your API key
|
|
310
668
|
base_url: Optional base URL
|
|
311
|
-
|
|
669
|
+
|
|
312
670
|
Returns:
|
|
313
671
|
A configured AsteroidClient instance
|
|
314
|
-
|
|
672
|
+
|
|
315
673
|
Example:
|
|
316
674
|
client = create_client('your-api-key')
|
|
317
675
|
"""
|
|
318
676
|
return AsteroidClient(api_key, base_url)
|
|
319
677
|
|
|
678
|
+
# --- V1 ---
|
|
679
|
+
|
|
320
680
|
def execute_agent(client: AsteroidClient, agent_id: str, execution_data: Dict[str, Any], agent_profile_id: Optional[str] = None) -> str:
|
|
321
681
|
"""
|
|
322
682
|
Execute an agent with the provided parameters.
|
|
323
|
-
|
|
683
|
+
|
|
324
684
|
Args:
|
|
325
685
|
client: The AsteroidClient instance
|
|
326
686
|
agent_id: The ID of the agent to execute
|
|
327
687
|
execution_data: The execution parameters
|
|
328
688
|
agent_profile_id: Optional ID of the agent profile
|
|
329
|
-
|
|
689
|
+
|
|
330
690
|
Returns:
|
|
331
691
|
The execution ID
|
|
332
|
-
|
|
692
|
+
|
|
333
693
|
Example:
|
|
334
694
|
execution_id = execute_agent(client, 'my-agent-id', {'input': 'some dynamic value'}, 'agent-profile-id')
|
|
335
695
|
"""
|
|
@@ -340,14 +700,14 @@ def execute_agent(client: AsteroidClient, agent_id: str, execution_data: Dict[st
|
|
|
340
700
|
def get_execution_status(client: AsteroidClient, execution_id: str) -> ExecutionStatusResponse:
|
|
341
701
|
"""
|
|
342
702
|
Get the current status for an execution.
|
|
343
|
-
|
|
703
|
+
|
|
344
704
|
Args:
|
|
345
705
|
client: The AsteroidClient instance
|
|
346
706
|
execution_id: The execution identifier
|
|
347
|
-
|
|
707
|
+
|
|
348
708
|
Returns:
|
|
349
709
|
The execution status details
|
|
350
|
-
|
|
710
|
+
|
|
351
711
|
Example:
|
|
352
712
|
status = get_execution_status(client, execution_id)
|
|
353
713
|
print(status.status)
|
|
@@ -355,72 +715,80 @@ def get_execution_status(client: AsteroidClient, execution_id: str) -> Execution
|
|
|
355
715
|
return client.get_execution_status(execution_id)
|
|
356
716
|
|
|
357
717
|
|
|
358
|
-
def get_execution_result(client: AsteroidClient, execution_id: str) ->
|
|
718
|
+
def get_execution_result(client: AsteroidClient, execution_id: str) -> ExecutionResult:
|
|
359
719
|
"""
|
|
360
720
|
Get the final result of an execution.
|
|
361
|
-
|
|
721
|
+
|
|
362
722
|
Args:
|
|
363
723
|
client: The AsteroidClient instance
|
|
364
724
|
execution_id: The execution identifier
|
|
365
|
-
|
|
725
|
+
|
|
366
726
|
Returns:
|
|
367
|
-
The result object
|
|
368
|
-
|
|
727
|
+
The execution result object
|
|
728
|
+
|
|
729
|
+
Raises:
|
|
730
|
+
Exception: If the result is not available yet or execution failed
|
|
731
|
+
|
|
369
732
|
Example:
|
|
370
733
|
result = get_execution_result(client, execution_id)
|
|
371
|
-
print(result)
|
|
734
|
+
print(result.outcome, result.reasoning)
|
|
372
735
|
"""
|
|
373
736
|
return client.get_execution_result(execution_id)
|
|
374
737
|
|
|
375
738
|
|
|
376
739
|
def wait_for_execution_result(
|
|
377
|
-
client: AsteroidClient,
|
|
378
|
-
execution_id: str,
|
|
379
|
-
interval: float = 1.0,
|
|
740
|
+
client: AsteroidClient,
|
|
741
|
+
execution_id: str,
|
|
742
|
+
interval: float = 1.0,
|
|
380
743
|
timeout: float = 3600.0
|
|
381
|
-
) ->
|
|
744
|
+
) -> ExecutionResult:
|
|
382
745
|
"""
|
|
383
746
|
Wait for an execution to reach a terminal state and return the result.
|
|
384
|
-
|
|
747
|
+
|
|
385
748
|
Args:
|
|
386
749
|
client: The AsteroidClient instance
|
|
387
750
|
execution_id: The execution identifier
|
|
388
751
|
interval: Polling interval in seconds (default is 1.0)
|
|
389
752
|
timeout: Maximum wait time in seconds (default is 3600 - 1 hour)
|
|
390
|
-
|
|
753
|
+
|
|
391
754
|
Returns:
|
|
392
|
-
The execution result
|
|
393
|
-
|
|
755
|
+
The execution result object
|
|
756
|
+
|
|
757
|
+
Raises:
|
|
758
|
+
TimeoutError: If the execution times out
|
|
759
|
+
ExecutionError: If the execution ends as "cancelled" or "failed"
|
|
760
|
+
|
|
394
761
|
Example:
|
|
395
762
|
result = wait_for_execution_result(client, execution_id, interval=2.0)
|
|
763
|
+
print(result.outcome, result.reasoning)
|
|
396
764
|
"""
|
|
397
765
|
return client.wait_for_execution_result(execution_id, interval, timeout)
|
|
398
766
|
|
|
399
767
|
|
|
400
768
|
def upload_execution_files(
|
|
401
|
-
client: AsteroidClient,
|
|
402
|
-
execution_id: str,
|
|
769
|
+
client: AsteroidClient,
|
|
770
|
+
execution_id: str,
|
|
403
771
|
files: List[Union[bytes, str, Tuple[str, bytes]]],
|
|
404
772
|
default_filename: str = "file.txt"
|
|
405
773
|
) -> UploadExecutionFiles200Response:
|
|
406
774
|
"""
|
|
407
775
|
Upload files to an execution.
|
|
408
|
-
|
|
776
|
+
|
|
409
777
|
Args:
|
|
410
778
|
client: The AsteroidClient instance
|
|
411
779
|
execution_id: The execution identifier
|
|
412
780
|
files: List of files to upload
|
|
413
781
|
default_filename: Default filename to use when file is provided as bytes
|
|
414
|
-
|
|
782
|
+
|
|
415
783
|
Returns:
|
|
416
784
|
The upload response containing message and file IDs
|
|
417
|
-
|
|
785
|
+
|
|
418
786
|
Example:
|
|
419
787
|
# Create a simple text file with "Hello World!" content
|
|
420
788
|
hello_content = "Hello World!".encode()
|
|
421
789
|
response = upload_execution_files(client, execution_id, [hello_content])
|
|
422
790
|
print(f"Uploaded files: {response.file_ids}")
|
|
423
|
-
|
|
791
|
+
|
|
424
792
|
# Or specify filename with content
|
|
425
793
|
files = [('hello.txt', "Hello World!".encode())]
|
|
426
794
|
response = upload_execution_files(client, execution_id, files)
|
|
@@ -431,20 +799,155 @@ def upload_execution_files(
|
|
|
431
799
|
def get_browser_session_recording(client: AsteroidClient, execution_id: str) -> str:
|
|
432
800
|
"""
|
|
433
801
|
Get the browser session recording URL for a completed execution.
|
|
434
|
-
|
|
802
|
+
|
|
435
803
|
Args:
|
|
436
804
|
client: The AsteroidClient instance
|
|
437
805
|
execution_id: The execution identifier
|
|
438
|
-
|
|
806
|
+
|
|
439
807
|
Returns:
|
|
440
808
|
The URL of the browser session recording
|
|
441
|
-
|
|
809
|
+
|
|
442
810
|
Example:
|
|
443
811
|
recording_url = get_browser_session_recording(client, execution_id)
|
|
444
812
|
print(f"Recording available at: {recording_url}")
|
|
445
813
|
"""
|
|
446
814
|
return client.get_browser_session_recording(execution_id)
|
|
447
815
|
|
|
816
|
+
def get_agent_profiles(client: AsteroidClient, organization_id: Optional[str] = None) -> List[AgentProfile]:
|
|
817
|
+
"""
|
|
818
|
+
Get a list of agent profiles.
|
|
819
|
+
Args:
|
|
820
|
+
client: The AsteroidClient instance
|
|
821
|
+
organization_id: The organization identifier (optional) Returns all agent profiles if no organization_id is provided.
|
|
822
|
+
Returns:
|
|
823
|
+
A list of agent profiles
|
|
824
|
+
Raises:
|
|
825
|
+
Exception: If the agent profiles request fails
|
|
826
|
+
Example:
|
|
827
|
+
profiles = get_agent_profiles(client, "org-123")
|
|
828
|
+
"""
|
|
829
|
+
return client.get_agent_profiles(organization_id)
|
|
830
|
+
def get_agent_profile(client: AsteroidClient, profile_id: str) -> AgentProfile:
|
|
831
|
+
"""
|
|
832
|
+
Get an agent profile by ID.
|
|
833
|
+
Args:
|
|
834
|
+
client: The AsteroidClient instance
|
|
835
|
+
profile_id: The ID of the agent profile
|
|
836
|
+
Returns:
|
|
837
|
+
The agent profile
|
|
838
|
+
Raises:
|
|
839
|
+
Exception: If the agent profile request fails
|
|
840
|
+
Example:
|
|
841
|
+
profile = get_agent_profile(client, "profile_id")
|
|
842
|
+
"""
|
|
843
|
+
return client.get_agent_profile(profile_id)
|
|
844
|
+
def create_agent_profile(client: AsteroidClient, request: CreateAgentProfileRequest) -> AgentProfile:
|
|
845
|
+
"""
|
|
846
|
+
Create an agent profile.
|
|
847
|
+
Args:
|
|
848
|
+
client: The AsteroidClient instance
|
|
849
|
+
request: The request object
|
|
850
|
+
Returns:
|
|
851
|
+
The agent profile
|
|
852
|
+
Raises:
|
|
853
|
+
Exception: If the agent profile creation fails
|
|
854
|
+
Example:
|
|
855
|
+
request = CreateAgentProfileRequest(
|
|
856
|
+
name="My Agent Profile",
|
|
857
|
+
description="This is my agent profile",
|
|
858
|
+
organization_id="org-123",
|
|
859
|
+
proxy_cc=CountryCode.US,
|
|
860
|
+
proxy_type=ProxyType.RESIDENTIAL,
|
|
861
|
+
captcha_solver_active=True,
|
|
862
|
+
sticky_ip=True,
|
|
863
|
+
credentials=[Credential(name="user", data="password")]
|
|
864
|
+
)
|
|
865
|
+
profile = create_agent_profile(client, request)
|
|
866
|
+
"""
|
|
867
|
+
return client.create_agent_profile(request)
|
|
868
|
+
def update_agent_profile(client: AsteroidClient, profile_id: str, request: UpdateAgentProfileRequest) -> AgentProfile:
|
|
869
|
+
"""
|
|
870
|
+
Update an agent profile with the provided request.
|
|
871
|
+
Args:
|
|
872
|
+
client: The AsteroidClient instance
|
|
873
|
+
profile_id: The ID of the agent profile
|
|
874
|
+
request: The request object
|
|
875
|
+
Returns:
|
|
876
|
+
The agent profile
|
|
877
|
+
Raises:
|
|
878
|
+
Exception: If the agent profile update fails
|
|
879
|
+
Example:
|
|
880
|
+
request = UpdateAgentProfileRequest(
|
|
881
|
+
name="My Agent Profile",
|
|
882
|
+
description="This is my agent profile",
|
|
883
|
+
organization_id="org-123",
|
|
884
|
+
)
|
|
885
|
+
profile = update_agent_profile(client, "profile_id", request)
|
|
886
|
+
"""
|
|
887
|
+
return client.update_agent_profile(profile_id, request)
|
|
888
|
+
def delete_agent_profile(client: AsteroidClient, profile_id: str) -> DeleteAgentProfile200Response:
|
|
889
|
+
"""
|
|
890
|
+
Delete an agent profile.
|
|
891
|
+
Args:
|
|
892
|
+
client: The AsteroidClient instance
|
|
893
|
+
profile_id: The ID of the agent profile
|
|
894
|
+
Returns:
|
|
895
|
+
The agent profile
|
|
896
|
+
Raises:
|
|
897
|
+
Exception: If the agent profile deletion fails
|
|
898
|
+
Example:
|
|
899
|
+
profile_deleted =delete_agent_profile(client, "profile_id")
|
|
900
|
+
"""
|
|
901
|
+
return client.delete_agent_profile(profile_id)
|
|
902
|
+
|
|
903
|
+
def get_credentials_public_key(client: AsteroidClient) -> str:
|
|
904
|
+
"""
|
|
905
|
+
Get the public key for encrypting credentials.
|
|
906
|
+
|
|
907
|
+
Args:
|
|
908
|
+
client: The AsteroidClient instance
|
|
909
|
+
|
|
910
|
+
Returns:
|
|
911
|
+
PEM-formatted RSA public key string
|
|
912
|
+
|
|
913
|
+
Example:
|
|
914
|
+
public_key = get_credentials_public_key(client)
|
|
915
|
+
"""
|
|
916
|
+
return client.get_credentials_public_key()
|
|
917
|
+
|
|
918
|
+
# --- V2 ---
|
|
919
|
+
|
|
920
|
+
def get_last_n_execution_activities(client: AsteroidClient, execution_id: str, n: int) -> List[ExecutionActivity]:
|
|
921
|
+
"""
|
|
922
|
+
Get the last N execution activities for a given execution ID, sorted by their timestamp in descending order.
|
|
923
|
+
Args:
|
|
924
|
+
client: The AsteroidClient instance
|
|
925
|
+
execution_id: The execution identifier
|
|
926
|
+
n: The number of activities to return
|
|
927
|
+
Returns:
|
|
928
|
+
A list of execution activities
|
|
929
|
+
Raises:
|
|
930
|
+
Exception: If the execution activities request fails
|
|
931
|
+
Example:
|
|
932
|
+
activities = get_last_n_execution_activities(client, "execution_id", 10)
|
|
933
|
+
"""
|
|
934
|
+
return client.get_last_n_execution_activities(execution_id, n)
|
|
935
|
+
|
|
936
|
+
def add_message_to_execution(client: AsteroidClient, execution_id: str, message: str) -> None:
|
|
937
|
+
"""
|
|
938
|
+
Add a message to an execution.
|
|
939
|
+
Args:
|
|
940
|
+
client: The AsteroidClient instance
|
|
941
|
+
execution_id: The execution identifier
|
|
942
|
+
message: The message to add
|
|
943
|
+
Returns:
|
|
944
|
+
None
|
|
945
|
+
Raises:
|
|
946
|
+
Exception: If the message addition fails
|
|
947
|
+
Example:
|
|
948
|
+
add_message_to_execution(client, "execution_id", "Hello, world!")
|
|
949
|
+
"""
|
|
950
|
+
return client.add_message_to_execution(execution_id, message)
|
|
448
951
|
|
|
449
952
|
# Re-export common types for convenience
|
|
450
953
|
__all__ = [
|
|
@@ -455,5 +958,14 @@ __all__ = [
|
|
|
455
958
|
'get_execution_result',
|
|
456
959
|
'wait_for_execution_result',
|
|
457
960
|
'upload_execution_files',
|
|
458
|
-
'get_browser_session_recording'
|
|
459
|
-
|
|
961
|
+
'get_browser_session_recording',
|
|
962
|
+
'get_agent_profiles',
|
|
963
|
+
'get_agent_profile',
|
|
964
|
+
'create_agent_profile',
|
|
965
|
+
'update_agent_profile',
|
|
966
|
+
'delete_agent_profile',
|
|
967
|
+
'get_credentials_public_key',
|
|
968
|
+
'AsteroidAPIError',
|
|
969
|
+
'ExecutionError',
|
|
970
|
+
'TimeoutError'
|
|
971
|
+
]
|