asteroid-odyssey 0.1.22__py3-none-any.whl → 0.4.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 +28 -34
- 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/agents_v1_gen/api/api_api.py +516 -0
- asteroid_odyssey/agents_v1_gen/api/execution_api.py +1734 -0
- asteroid_odyssey/agents_v1_gen/api_client.py +801 -0
- asteroid_odyssey/agents_v1_gen/api_response.py +21 -0
- asteroid_odyssey/agents_v1_gen/configuration.py +606 -0
- asteroid_odyssey/agents_v1_gen/exceptions.py +216 -0
- 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/browser_session_recording_response.py +87 -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/agents_v1_gen/models/error_response.py +87 -0
- asteroid_odyssey/agents_v1_gen/models/execution_response.py +87 -0
- asteroid_odyssey/agents_v1_gen/models/execution_result.py +101 -0
- asteroid_odyssey/agents_v1_gen/models/execution_result_response.py +100 -0
- asteroid_odyssey/agents_v1_gen/models/execution_status_response.py +95 -0
- asteroid_odyssey/agents_v1_gen/models/health_check200_response.py +87 -0
- asteroid_odyssey/agents_v1_gen/models/health_check500_response.py +87 -0
- asteroid_odyssey/agents_v1_gen/models/proxy_type.py +37 -0
- asteroid_odyssey/agents_v1_gen/models/status.py +43 -0
- asteroid_odyssey/agents_v1_gen/models/structured_agent_execution_request.py +89 -0
- asteroid_odyssey/agents_v1_gen/models/update_agent_profile_request.py +112 -0
- asteroid_odyssey/agents_v1_gen/models/upload_execution_files200_response.py +89 -0
- asteroid_odyssey/agents_v1_gen/rest.py +258 -0
- 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/rest.py +258 -0
- asteroid_odyssey/client.py +881 -304
- asteroid_odyssey-0.4.0.dist-info/METADATA +213 -0
- asteroid_odyssey-0.4.0.dist-info/RECORD +72 -0
- {asteroid_odyssey-0.1.22.dist-info → asteroid_odyssey-0.4.0.dist-info}/WHEEL +1 -1
- asteroid_odyssey/api/generated/asteroid_agents_api_client/__init__.py +0 -8
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/__init__.py +0 -1
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/agent/get_agents.py +0 -127
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/api/get_open_api.py +0 -79
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/api/health_check.py +0 -131
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/credentials/__init__.py +0 -0
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/credentials/get_credentials_public_key.py +0 -127
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/__init__.py +0 -0
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/delete_execution.py +0 -156
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_browser_session.py +0 -154
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution.py +0 -154
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution_files.py +0 -159
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution_progress.py +0 -159
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_executions_for_workflow.py +0 -156
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/update_execution_status.py +0 -115
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/notifications/__init__.py +0 -0
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/notifications/set_slack_channel.py +0 -107
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/optimiser/__init__.py +0 -0
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/optimiser/queue_optimisation_job.py +0 -103
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/__init__.py +0 -0
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/add_workflow_credential.py +0 -111
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/create_workflow.py +0 -174
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/delete_workflow.py +0 -156
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/delete_workflow_credentials.py +0 -96
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/execute_workflow.py +0 -179
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_agent_workflow_executions.py +0 -155
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow.py +0 -154
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow_credentials.py +0 -154
- asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow_versions.py +0 -159
- asteroid_odyssey/api/generated/asteroid_agents_api_client/client.py +0 -268
- asteroid_odyssey/api/generated/asteroid_agents_api_client/errors.py +0 -16
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/__init__.py +0 -61
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/agent.py +0 -90
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/browser_session.py +0 -136
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request.py +0 -126
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request_fields.py +0 -48
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request_provider.py +0 -9
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credential.py +0 -66
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credentials_request.py +0 -72
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credentials_response.py +0 -80
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_execution_response_200.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_execution_response_404.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_workflow_response_200.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_workflow_response_404.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution.py +0 -147
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_dynamic_data.py +0 -48
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_result.py +0 -43
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_status.py +0 -89
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/file.py +0 -127
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/health_check_response_200.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/health_check_response_500.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/optimisation_request.py +0 -59
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/progress_update.py +0 -77
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/result_schema.py +0 -51
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/slack_channel_request.py +0 -58
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/status.py +0 -13
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow.py +0 -152
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_execution.py +0 -82
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_execution_request.py +0 -48
- asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_fields.py +0 -48
- asteroid_odyssey/api/generated/asteroid_agents_api_client/py.typed +0 -1
- asteroid_odyssey/api/generated/asteroid_agents_api_client/types.py +0 -45
- asteroid_odyssey/exceptions.py +0 -15
- asteroid_odyssey-0.1.22.dist-info/METADATA +0 -31
- asteroid_odyssey-0.1.22.dist-info/RECORD +0 -72
- asteroid_odyssey-0.1.22.dist-info/entry_points.txt +0 -2
- /asteroid_odyssey/{api/generated/asteroid_agents_api_client/api/agent/__init__.py → agents_v1_gen/py.typed} +0 -0
- /asteroid_odyssey/{api/generated/asteroid_agents_api_client/api/api/__init__.py → agents_v2_gen/py.typed} +0 -0
- {asteroid_odyssey-0.1.22.dist-info → asteroid_odyssey-0.4.0.dist-info}/top_level.txt +0 -0
asteroid_odyssey/client.py
CHANGED
|
@@ -1,394 +1,971 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
Asteroid Agents Python SDK - High-Level Client Interface
|
|
3
|
+
|
|
4
|
+
Provides a clean, easy-to-use interface for interacting with the Asteroid Agents API,
|
|
5
|
+
similar to the TypeScript SDK.
|
|
6
|
+
|
|
7
|
+
This module provides a high-level client that wraps the generated OpenAPI client
|
|
8
|
+
without modifying any generated files.
|
|
9
|
+
"""
|
|
10
|
+
|
|
4
11
|
import time
|
|
5
|
-
from enum import Enum
|
|
6
12
|
import os
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import logging
|
|
14
|
+
import base64
|
|
15
|
+
from typing import Dict, Any, Optional, List, Union, Tuple
|
|
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,
|
|
24
|
+
ExecutionStatusResponse,
|
|
25
|
+
ExecutionResult,
|
|
26
|
+
UploadExecutionFiles200Response,
|
|
27
|
+
Status,
|
|
28
|
+
StructuredAgentExecutionRequest,
|
|
29
|
+
CreateAgentProfileRequest,
|
|
30
|
+
UpdateAgentProfileRequest,
|
|
31
|
+
DeleteAgentProfile200Response,
|
|
32
|
+
AgentProfile,
|
|
33
|
+
Credential,
|
|
34
|
+
)
|
|
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,
|
|
18
42
|
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
98
|
+
|
|
45
99
|
|
|
46
100
|
class AsteroidClient:
|
|
47
101
|
"""
|
|
48
|
-
|
|
102
|
+
High-level client for the Asteroid Agents API.
|
|
103
|
+
|
|
104
|
+
This class provides a convenient interface for executing agents and managing
|
|
105
|
+
their execution lifecycle, similar to the TypeScript SDK.
|
|
49
106
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
api_key: Optional[str] = None,
|
|
53
|
-
base_url: Optional[str] = None,
|
|
54
|
-
verify_ssl: bool = True
|
|
55
|
-
):
|
|
107
|
+
|
|
108
|
+
def __init__(self, api_key: str, base_url: Optional[str] = None):
|
|
56
109
|
"""
|
|
57
|
-
|
|
110
|
+
Create an API client with the provided API key.
|
|
58
111
|
|
|
59
112
|
Args:
|
|
60
|
-
api_key: API key for authentication
|
|
61
|
-
base_url:
|
|
62
|
-
|
|
113
|
+
api_key: Your API key for authentication
|
|
114
|
+
base_url: Optional base URL (defaults to https://odyssey.asteroid.ai/api/v1)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
client = AsteroidClient('your-api-key')
|
|
63
118
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raise ValueError(
|
|
67
|
-
"API key is required. Either pass it directly or set ASTEROID_API_KEY environment variable"
|
|
68
|
-
)
|
|
119
|
+
if api_key is None:
|
|
120
|
+
raise TypeError("API key cannot be None")
|
|
69
121
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
verify_ssl=verify_ssl,
|
|
75
|
-
headers={"X-Asteroid-Agents-Api-Key": f"{self.api_key}"}
|
|
122
|
+
# Configure the API client
|
|
123
|
+
config = AgentsV1Configuration(
|
|
124
|
+
host=base_url or "https://odyssey.asteroid.ai/api/v1",
|
|
125
|
+
api_key={'ApiKeyAuth': api_key}
|
|
76
126
|
)
|
|
77
127
|
|
|
78
|
-
|
|
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:
|
|
79
143
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
144
|
+
Execute an agent with the provided parameters.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
agent_id: The ID of the agent to execute
|
|
148
|
+
execution_data: The execution parameters
|
|
149
|
+
agent_profile_id: Optional ID of the agent profile
|
|
150
|
+
|
|
82
151
|
Returns:
|
|
83
|
-
|
|
152
|
+
The execution ID
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
AsteroidAPIError: If the execution request fails
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
execution_id = client.execute_agent('my-agent-id', {'input': 'some dynamic value'}, 'agent-profile-id')
|
|
84
159
|
"""
|
|
160
|
+
req = StructuredAgentExecutionRequest(dynamic_data=execution_data, agent_profile_id=agent_profile_id)
|
|
85
161
|
try:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
except Exception as e:
|
|
93
|
-
logger.error(f"Failed to get agents: {str(e)}")
|
|
94
|
-
raise
|
|
95
|
-
|
|
96
|
-
def create_workflow(
|
|
97
|
-
self,
|
|
98
|
-
workflow_name: str,
|
|
99
|
-
start_url: str,
|
|
100
|
-
prompt: str,
|
|
101
|
-
result_schema: Optional[ResultSchema] = None
|
|
102
|
-
) -> str:
|
|
162
|
+
response = self.execution_api.execute_agent_structured(agent_id, req)
|
|
163
|
+
return response.execution_id
|
|
164
|
+
except ApiException as e:
|
|
165
|
+
raise AsteroidAPIError(f"Failed to execute agent: {e}") from e
|
|
166
|
+
|
|
167
|
+
def get_execution_status(self, execution_id: str) -> ExecutionStatusResponse:
|
|
103
168
|
"""
|
|
104
|
-
|
|
169
|
+
Get the current status for an execution.
|
|
105
170
|
|
|
106
171
|
Args:
|
|
107
|
-
|
|
108
|
-
start_url: Starting URL for the workflow
|
|
109
|
-
prompt: Prompt for the workflow
|
|
110
|
-
result_schema: Optional custom result schema. Currently not fully supported.
|
|
172
|
+
execution_id: The execution identifier
|
|
111
173
|
|
|
112
174
|
Returns:
|
|
113
|
-
|
|
175
|
+
The execution status details
|
|
114
176
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Currently, only the default schema will be used regardless of input.
|
|
118
|
-
"""
|
|
119
|
-
if result_schema is not None:
|
|
120
|
-
logger.warning("Custom result schemas are not fully supported yet and will be ignored. Using default schema.")
|
|
121
|
-
|
|
122
|
-
# Default result schema
|
|
123
|
-
default_schema = ResultSchema()
|
|
124
|
-
default_schema.additional_properties = {
|
|
125
|
-
"properties": {
|
|
126
|
-
"explanation": {
|
|
127
|
-
"description": "Detailed explanation of the result",
|
|
128
|
-
"type": "string"
|
|
129
|
-
},
|
|
130
|
-
"success": {
|
|
131
|
-
"description": "Whether the operation was successful",
|
|
132
|
-
"type": "boolean"
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
"required": [
|
|
136
|
-
"explanation",
|
|
137
|
-
"success"
|
|
138
|
-
],
|
|
139
|
-
"type": "object"
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if result_schema is None:
|
|
143
|
-
result_schema = default_schema
|
|
177
|
+
Raises:
|
|
178
|
+
AsteroidAPIError: If the status request fails
|
|
144
179
|
|
|
180
|
+
Example:
|
|
181
|
+
status = client.get_execution_status(execution_id)
|
|
182
|
+
print(status.status)
|
|
183
|
+
"""
|
|
145
184
|
try:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"start_url": start_url
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
request = CreateWorkflowRequest(
|
|
153
|
-
name=workflow_name,
|
|
154
|
-
result_schema=result_schema,
|
|
155
|
-
fields=fields,
|
|
156
|
-
prompts=[prompt],
|
|
157
|
-
provider=CreateWorkflowRequestProvider.OPENAI
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
result = create_workflow(
|
|
161
|
-
agent_name=agent_name,
|
|
162
|
-
body=request,
|
|
163
|
-
client=self.client
|
|
164
|
-
).parsed
|
|
165
|
-
if not isinstance(result, str):
|
|
166
|
-
raise ValueError("The result is not of type str")
|
|
167
|
-
return result
|
|
168
|
-
except Exception as e:
|
|
169
|
-
logger.error(f"Failed to create workflow: {str(e)}")
|
|
170
|
-
raise
|
|
185
|
+
return self.execution_api.get_execution_status(execution_id)
|
|
186
|
+
except ApiException as e:
|
|
187
|
+
raise AsteroidAPIError(f"Failed to get execution status: {e}") from e
|
|
171
188
|
|
|
172
|
-
def
|
|
173
|
-
self,
|
|
174
|
-
workflow_id: UUID,
|
|
175
|
-
execution_params: Dict[str, Any]
|
|
176
|
-
) -> str:
|
|
189
|
+
def get_execution_result(self, execution_id: str) -> ExecutionResult:
|
|
177
190
|
"""
|
|
178
|
-
|
|
191
|
+
Get the final result of an execution.
|
|
179
192
|
|
|
180
193
|
Args:
|
|
181
|
-
|
|
182
|
-
execution_params: Parameters for workflow execution
|
|
194
|
+
execution_id: The execution identifier
|
|
183
195
|
|
|
184
196
|
Returns:
|
|
185
|
-
|
|
197
|
+
The execution result object
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
AsteroidAPIError: If the result request fails or execution failed
|
|
201
|
+
|
|
202
|
+
Example:
|
|
203
|
+
result = client.get_execution_result(execution_id)
|
|
204
|
+
print(result.outcome, result.reasoning)
|
|
186
205
|
"""
|
|
187
206
|
try:
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
response = self.execution_api.get_execution_result(execution_id)
|
|
208
|
+
|
|
209
|
+
if response.error:
|
|
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")
|
|
190
215
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
client=self.client
|
|
195
|
-
).parsed
|
|
196
|
-
if not isinstance(result, str):
|
|
197
|
-
raise ValueError("The result is not of type str")
|
|
198
|
-
return result
|
|
216
|
+
return response.execution_result
|
|
217
|
+
except ApiException as e:
|
|
218
|
+
raise AsteroidAPIError(f"Failed to get execution result: {e}") from e
|
|
199
219
|
except Exception as e:
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
202
224
|
|
|
203
|
-
def
|
|
225
|
+
def wait_for_execution_result(
|
|
226
|
+
self,
|
|
227
|
+
execution_id: str,
|
|
228
|
+
interval: float = 1.0,
|
|
229
|
+
timeout: float = 3600.0
|
|
230
|
+
) -> ExecutionResult:
|
|
204
231
|
"""
|
|
205
|
-
|
|
232
|
+
Wait for an execution to reach a terminal state and return the result.
|
|
233
|
+
|
|
234
|
+
Continuously polls the execution status until it's either "completed",
|
|
235
|
+
"cancelled", or "failed".
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
execution_id: The execution identifier
|
|
239
|
+
interval: Polling interval in seconds (default is 1.0)
|
|
240
|
+
timeout: Maximum wait time in seconds (default is 3600 - 1 hour)
|
|
206
241
|
|
|
207
242
|
Returns:
|
|
208
|
-
|
|
243
|
+
The execution result object
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
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
|
+
|
|
250
|
+
Example:
|
|
251
|
+
result = client.wait_for_execution_result(execution_id, interval=2.0)
|
|
252
|
+
print(result.outcome, result.reasoning)
|
|
209
253
|
"""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
raise ValueError("The result is not of type List")
|
|
217
|
-
return result
|
|
218
|
-
except Exception as e:
|
|
219
|
-
logger.error(f"Failed to get workflow executions: {str(e)}")
|
|
220
|
-
raise
|
|
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")
|
|
259
|
+
start_time = time.time()
|
|
221
260
|
|
|
222
|
-
|
|
261
|
+
while True:
|
|
262
|
+
elapsed_time = time.time() - start_time
|
|
263
|
+
if elapsed_time >= timeout:
|
|
264
|
+
raise TimeoutError(f"Execution {execution_id} timed out after {timeout}s")
|
|
265
|
+
|
|
266
|
+
status_response = self.get_execution_status(execution_id)
|
|
267
|
+
current_status = status_response.status
|
|
268
|
+
|
|
269
|
+
if current_status == Status.COMPLETED:
|
|
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
|
|
278
|
+
elif current_status in [Status.FAILED, Status.CANCELLED]:
|
|
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
|
+
|
|
292
|
+
# Wait for the specified interval before polling again
|
|
293
|
+
time.sleep(interval)
|
|
294
|
+
|
|
295
|
+
def upload_execution_files(
|
|
296
|
+
self,
|
|
297
|
+
execution_id: str,
|
|
298
|
+
files: List[Union[bytes, str, Tuple[str, bytes]]],
|
|
299
|
+
default_filename: str = "file.txt"
|
|
300
|
+
) -> UploadExecutionFiles200Response:
|
|
223
301
|
"""
|
|
224
|
-
|
|
302
|
+
Upload files to an execution.
|
|
225
303
|
|
|
226
304
|
Args:
|
|
227
|
-
execution_id:
|
|
305
|
+
execution_id: The execution identifier
|
|
306
|
+
files: List of files to upload. Each file can be:
|
|
307
|
+
- bytes: Raw file content (will use default_filename)
|
|
308
|
+
- str: File path as string (will read file and use filename)
|
|
309
|
+
- Tuple[str, bytes]: (filename, file_content) tuple
|
|
310
|
+
default_filename: Default filename to use when file is provided as bytes
|
|
228
311
|
|
|
229
312
|
Returns:
|
|
230
|
-
|
|
313
|
+
The upload response containing message and file IDs
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
Exception: If the upload request fails
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
# Upload with file content (file should be in your current working directory)
|
|
320
|
+
with open('hello.txt', 'r') as f:
|
|
321
|
+
file_content = f.read()
|
|
322
|
+
|
|
323
|
+
response = client.upload_execution_files(execution_id, [file_content.encode()])
|
|
324
|
+
print(f"Uploaded files: {response.file_ids}")
|
|
325
|
+
|
|
326
|
+
# Upload with filename and content
|
|
327
|
+
files = [('hello.txt', file_content.encode())]
|
|
328
|
+
response = client.upload_execution_files(execution_id, files)
|
|
329
|
+
|
|
330
|
+
# Or create content directly
|
|
331
|
+
hello_content = "Hello World!".encode()
|
|
332
|
+
response = client.upload_execution_files(execution_id, [hello_content])
|
|
231
333
|
"""
|
|
232
334
|
try:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
335
|
+
# Process files to ensure proper format
|
|
336
|
+
processed_files = []
|
|
337
|
+
for file_item in files:
|
|
338
|
+
if isinstance(file_item, tuple):
|
|
339
|
+
# Already in (filename, content) format
|
|
340
|
+
filename, content = file_item
|
|
341
|
+
if isinstance(content, str):
|
|
342
|
+
content = content.encode()
|
|
343
|
+
processed_files.append((filename, content))
|
|
344
|
+
elif isinstance(file_item, str):
|
|
345
|
+
# Check if string is a file path that exists, otherwise treat as content
|
|
346
|
+
if os.path.isfile(file_item):
|
|
347
|
+
# File path - read the file
|
|
348
|
+
filename = os.path.basename(file_item)
|
|
349
|
+
with open(file_item, 'rb') as f:
|
|
350
|
+
content = f.read()
|
|
351
|
+
processed_files.append((filename, content))
|
|
352
|
+
else:
|
|
353
|
+
# String content - encode and use default filename
|
|
354
|
+
content = file_item.encode()
|
|
355
|
+
processed_files.append((default_filename, content))
|
|
356
|
+
elif isinstance(file_item, bytes):
|
|
357
|
+
# Raw bytes - use default filename
|
|
358
|
+
processed_files.append((default_filename, file_item))
|
|
359
|
+
else:
|
|
360
|
+
# Other types - convert to string content and encode
|
|
361
|
+
content = str(file_item).encode()
|
|
362
|
+
processed_files.append((default_filename, content))
|
|
363
|
+
|
|
364
|
+
response = self.execution_api.upload_execution_files(execution_id, files=processed_files)
|
|
365
|
+
return response
|
|
366
|
+
except ApiException as e:
|
|
367
|
+
raise AsteroidAPIError(f"Failed to upload execution files: {e}") from e
|
|
368
|
+
|
|
369
|
+
def get_browser_session_recording(self, execution_id: str) -> str:
|
|
242
370
|
"""
|
|
243
|
-
Get the
|
|
371
|
+
Get the browser session recording URL for a completed execution.
|
|
244
372
|
|
|
245
373
|
Args:
|
|
246
|
-
execution_id:
|
|
374
|
+
execution_id: The execution identifier
|
|
247
375
|
|
|
248
376
|
Returns:
|
|
249
|
-
|
|
377
|
+
The URL of the browser session recording
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
Exception: If the recording request fails
|
|
381
|
+
|
|
382
|
+
Example:
|
|
383
|
+
recording_url = client.get_browser_session_recording(execution_id)
|
|
384
|
+
print(f"Recording available at: {recording_url}")
|
|
250
385
|
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
386
|
+
try:
|
|
387
|
+
response = self.execution_api.get_browser_session_recording(execution_id)
|
|
388
|
+
return response.recording_url
|
|
389
|
+
except ApiException as e:
|
|
390
|
+
raise AsteroidAPIError(f"Failed to get browser session recording: {e}") from e
|
|
255
391
|
|
|
256
|
-
def
|
|
392
|
+
def get_agent_profiles(self, organization_id: str) -> List[AgentProfile]:
|
|
257
393
|
"""
|
|
258
|
-
Get
|
|
394
|
+
Get a list of agent profiles for a specific organization.
|
|
259
395
|
|
|
260
396
|
Args:
|
|
261
|
-
|
|
262
|
-
|
|
397
|
+
organization_id: The organization identifier (required)
|
|
263
398
|
Returns:
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
266
417
|
Raises:
|
|
267
|
-
|
|
418
|
+
Exception: If the agent profile request fails
|
|
419
|
+
Example:
|
|
420
|
+
profile = client.get_agent_profile("profile_id")
|
|
268
421
|
"""
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
271
427
|
|
|
272
|
-
def
|
|
273
|
-
self,
|
|
274
|
-
execution_id: str,
|
|
275
|
-
polling_interval: float = 1.0,
|
|
276
|
-
timeout: Optional[float] = None,
|
|
277
|
-
status_callback: Optional[Callable[[ExecutionStatus], None]] = None
|
|
278
|
-
) -> ExecutionStatus:
|
|
428
|
+
def create_agent_profile(self, request: CreateAgentProfileRequest) -> AgentProfile:
|
|
279
429
|
"""
|
|
280
|
-
|
|
430
|
+
Create an agent profile with automatic credential encryption.
|
|
281
431
|
|
|
282
432
|
Args:
|
|
283
|
-
|
|
284
|
-
polling_interval: Time in seconds between status checks
|
|
285
|
-
timeout: Maximum time in seconds to wait. None means wait indefinitely
|
|
286
|
-
status_callback: Optional callback function that will be called with each status update
|
|
287
|
-
|
|
433
|
+
request: The request object
|
|
288
434
|
Returns:
|
|
289
|
-
|
|
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.
|
|
290
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
|
|
291
549
|
Raises:
|
|
292
|
-
|
|
293
|
-
|
|
550
|
+
Exception: If the agent profile deletion fails
|
|
551
|
+
Example:
|
|
552
|
+
response = client.delete_agent_profile("profile_id")
|
|
294
553
|
"""
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
297
559
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
raise TimeoutError(f"Execution {execution_id} did not complete within {timeout} seconds")
|
|
560
|
+
def get_credentials_public_key(self) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Get the public key for encrypting credentials.
|
|
302
563
|
|
|
303
|
-
|
|
304
|
-
|
|
564
|
+
Returns:
|
|
565
|
+
PEM-formatted RSA public key string
|
|
305
566
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
status_callback(current_status)
|
|
309
|
-
last_status = current_status
|
|
567
|
+
Raises:
|
|
568
|
+
Exception: If the public key request fails
|
|
310
569
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
314
578
|
|
|
315
|
-
|
|
316
|
-
|
|
579
|
+
def __enter__(self):
|
|
580
|
+
"""Context manager entry."""
|
|
581
|
+
return self
|
|
317
582
|
|
|
318
|
-
def
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
583
|
+
def __exit__(self, exc_type, exc_value, tb):
|
|
584
|
+
"""Context manager exit: clean up API client connection pool."""
|
|
585
|
+
try:
|
|
586
|
+
# Try to grab the pool_manager; if any attr is missing, skip
|
|
587
|
+
try:
|
|
588
|
+
pool_manager = self.api_client.rest_client.pool_manager
|
|
589
|
+
except AttributeError:
|
|
590
|
+
pool_manager = None
|
|
591
|
+
|
|
592
|
+
if pool_manager:
|
|
593
|
+
pool_manager.clear()
|
|
594
|
+
except Exception as e:
|
|
595
|
+
# Log but don't mask the original exception (if any)
|
|
596
|
+
logging.warning("Failed to clear connection pool: %s", e)
|
|
597
|
+
|
|
598
|
+
# Returning False allows any exception in the 'with' block to propagate
|
|
599
|
+
return False
|
|
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]:
|
|
325
631
|
"""
|
|
326
|
-
|
|
327
|
-
|
|
632
|
+
Get the last N execution activities for a given execution ID, sorted by their timestamp in descending order.
|
|
328
633
|
Args:
|
|
329
|
-
execution_id:
|
|
330
|
-
|
|
331
|
-
timeout: Maximum time in seconds to wait. None means wait indefinitely
|
|
332
|
-
status_callback: Optional callback function that will be called with each status update
|
|
333
|
-
|
|
634
|
+
execution_id: The execution identifier
|
|
635
|
+
n: The number of activities to return
|
|
334
636
|
Returns:
|
|
335
|
-
|
|
336
|
-
|
|
637
|
+
A list of execution activities
|
|
337
638
|
Raises:
|
|
338
|
-
|
|
339
|
-
ValueError: If execution_id is invalid
|
|
639
|
+
Exception: If the execution activities request fails
|
|
340
640
|
"""
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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)
|
|
348
657
|
|
|
349
|
-
# Get the final result
|
|
350
|
-
result = self.get_execution_result(execution_id)
|
|
351
658
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
659
|
+
# Convenience functions that mirror the TypeScript SDK pattern
|
|
660
|
+
def create_client(api_key: str, base_url: Optional[str] = None) -> AsteroidClient:
|
|
661
|
+
"""
|
|
662
|
+
Create an API client with a provided API key.
|
|
355
663
|
|
|
356
|
-
|
|
664
|
+
This is a convenience function that creates an AsteroidClient instance.
|
|
357
665
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
execution_params: Dict[str, Any],
|
|
362
|
-
polling_interval: float = 1.0,
|
|
363
|
-
timeout: Optional[float] = None,
|
|
364
|
-
status_callback: Optional[Callable[[ExecutionStatus], None]] = None
|
|
365
|
-
) -> ExecutionResult:
|
|
366
|
-
"""
|
|
367
|
-
Execute a workflow and wait for its result.
|
|
666
|
+
Args:
|
|
667
|
+
api_key: Your API key
|
|
668
|
+
base_url: Optional base URL
|
|
368
669
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
execution_params: Parameters for workflow execution
|
|
372
|
-
polling_interval: Time in seconds between status checks
|
|
373
|
-
timeout: Maximum time in seconds to wait. None means wait indefinitely
|
|
374
|
-
status_callback: Optional callback function that will be called with each status update
|
|
670
|
+
Returns:
|
|
671
|
+
A configured AsteroidClient instance
|
|
375
672
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
673
|
+
Example:
|
|
674
|
+
client = create_client('your-api-key')
|
|
675
|
+
"""
|
|
676
|
+
return AsteroidClient(api_key, base_url)
|
|
677
|
+
|
|
678
|
+
# --- V1 ---
|
|
679
|
+
|
|
680
|
+
def execute_agent(client: AsteroidClient, agent_id: str, execution_data: Dict[str, Any], agent_profile_id: Optional[str] = None) -> str:
|
|
681
|
+
"""
|
|
682
|
+
Execute an agent with the provided parameters.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
client: The AsteroidClient instance
|
|
686
|
+
agent_id: The ID of the agent to execute
|
|
687
|
+
execution_data: The execution parameters
|
|
688
|
+
agent_profile_id: Optional ID of the agent profile
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
The execution ID
|
|
692
|
+
|
|
693
|
+
Example:
|
|
694
|
+
execution_id = execute_agent(client, 'my-agent-id', {'input': 'some dynamic value'}, 'agent-profile-id')
|
|
695
|
+
"""
|
|
696
|
+
return client.execute_agent(agent_id, execution_data, agent_profile_id)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def get_execution_status(client: AsteroidClient, execution_id: str) -> ExecutionStatusResponse:
|
|
701
|
+
"""
|
|
702
|
+
Get the current status for an execution.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
client: The AsteroidClient instance
|
|
706
|
+
execution_id: The execution identifier
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
The execution status details
|
|
710
|
+
|
|
711
|
+
Example:
|
|
712
|
+
status = get_execution_status(client, execution_id)
|
|
713
|
+
print(status.status)
|
|
714
|
+
"""
|
|
715
|
+
return client.get_execution_status(execution_id)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def get_execution_result(client: AsteroidClient, execution_id: str) -> ExecutionResult:
|
|
719
|
+
"""
|
|
720
|
+
Get the final result of an execution.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
client: The AsteroidClient instance
|
|
724
|
+
execution_id: The execution identifier
|
|
725
|
+
|
|
726
|
+
Returns:
|
|
727
|
+
The execution result object
|
|
728
|
+
|
|
729
|
+
Raises:
|
|
730
|
+
Exception: If the result is not available yet or execution failed
|
|
731
|
+
|
|
732
|
+
Example:
|
|
733
|
+
result = get_execution_result(client, execution_id)
|
|
734
|
+
print(result.outcome, result.reasoning)
|
|
735
|
+
"""
|
|
736
|
+
return client.get_execution_result(execution_id)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def wait_for_execution_result(
|
|
740
|
+
client: AsteroidClient,
|
|
741
|
+
execution_id: str,
|
|
742
|
+
interval: float = 1.0,
|
|
743
|
+
timeout: float = 3600.0
|
|
744
|
+
) -> ExecutionResult:
|
|
745
|
+
"""
|
|
746
|
+
Wait for an execution to reach a terminal state and return the result.
|
|
747
|
+
|
|
748
|
+
Args:
|
|
749
|
+
client: The AsteroidClient instance
|
|
750
|
+
execution_id: The execution identifier
|
|
751
|
+
interval: Polling interval in seconds (default is 1.0)
|
|
752
|
+
timeout: Maximum wait time in seconds (default is 3600 - 1 hour)
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
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
|
+
|
|
761
|
+
Example:
|
|
762
|
+
result = wait_for_execution_result(client, execution_id, interval=2.0)
|
|
763
|
+
print(result.outcome, result.reasoning)
|
|
764
|
+
"""
|
|
765
|
+
return client.wait_for_execution_result(execution_id, interval, timeout)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def upload_execution_files(
|
|
769
|
+
client: AsteroidClient,
|
|
770
|
+
execution_id: str,
|
|
771
|
+
files: List[Union[bytes, str, Tuple[str, bytes]]],
|
|
772
|
+
default_filename: str = "file.txt"
|
|
773
|
+
) -> UploadExecutionFiles200Response:
|
|
774
|
+
"""
|
|
775
|
+
Upload files to an execution.
|
|
776
|
+
|
|
777
|
+
Args:
|
|
778
|
+
client: The AsteroidClient instance
|
|
779
|
+
execution_id: The execution identifier
|
|
780
|
+
files: List of files to upload
|
|
781
|
+
default_filename: Default filename to use when file is provided as bytes
|
|
782
|
+
|
|
783
|
+
Returns:
|
|
784
|
+
The upload response containing message and file IDs
|
|
785
|
+
|
|
786
|
+
Example:
|
|
787
|
+
# Create a simple text file with "Hello World!" content
|
|
788
|
+
hello_content = "Hello World!".encode()
|
|
789
|
+
response = upload_execution_files(client, execution_id, [hello_content])
|
|
790
|
+
print(f"Uploaded files: {response.file_ids}")
|
|
791
|
+
|
|
792
|
+
# Or specify filename with content
|
|
793
|
+
files = [('hello.txt', "Hello World!".encode())]
|
|
794
|
+
response = upload_execution_files(client, execution_id, files)
|
|
795
|
+
"""
|
|
796
|
+
return client.upload_execution_files(execution_id, files, default_filename)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def get_browser_session_recording(client: AsteroidClient, execution_id: str) -> str:
|
|
800
|
+
"""
|
|
801
|
+
Get the browser session recording URL for a completed execution.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
client: The AsteroidClient instance
|
|
805
|
+
execution_id: The execution identifier
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
The URL of the browser session recording
|
|
809
|
+
|
|
810
|
+
Example:
|
|
811
|
+
recording_url = get_browser_session_recording(client, execution_id)
|
|
812
|
+
print(f"Recording available at: {recording_url}")
|
|
813
|
+
"""
|
|
814
|
+
return client.get_browser_session_recording(execution_id)
|
|
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")]
|
|
388
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)
|
|
389
902
|
|
|
390
|
-
|
|
391
|
-
|
|
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()
|
|
392
917
|
|
|
393
|
-
|
|
394
|
-
|
|
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)
|
|
951
|
+
|
|
952
|
+
# Re-export common types for convenience
|
|
953
|
+
__all__ = [
|
|
954
|
+
'AsteroidClient',
|
|
955
|
+
'create_client',
|
|
956
|
+
'execute_agent',
|
|
957
|
+
'get_execution_status',
|
|
958
|
+
'get_execution_result',
|
|
959
|
+
'wait_for_execution_result',
|
|
960
|
+
'upload_execution_files',
|
|
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
|
+
]
|