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.
Files changed (138) hide show
  1. asteroid_odyssey/__init__.py +28 -34
  2. asteroid_odyssey/agents_v1_gen/__init__.py +87 -0
  3. asteroid_odyssey/agents_v1_gen/api/__init__.py +7 -0
  4. asteroid_odyssey/agents_v1_gen/api/agent_profile_api.py +1696 -0
  5. asteroid_odyssey/agents_v1_gen/api/api_api.py +516 -0
  6. asteroid_odyssey/agents_v1_gen/api/execution_api.py +1734 -0
  7. asteroid_odyssey/agents_v1_gen/api_client.py +801 -0
  8. asteroid_odyssey/agents_v1_gen/api_response.py +21 -0
  9. asteroid_odyssey/agents_v1_gen/configuration.py +606 -0
  10. asteroid_odyssey/agents_v1_gen/exceptions.py +216 -0
  11. asteroid_odyssey/agents_v1_gen/models/__init__.py +34 -0
  12. asteroid_odyssey/agents_v1_gen/models/agent_profile.py +118 -0
  13. asteroid_odyssey/agents_v1_gen/models/browser_session_recording_response.py +87 -0
  14. asteroid_odyssey/agents_v1_gen/models/country_code.py +44 -0
  15. asteroid_odyssey/agents_v1_gen/models/create_agent_profile_request.py +112 -0
  16. asteroid_odyssey/agents_v1_gen/models/credential.py +95 -0
  17. asteroid_odyssey/agents_v1_gen/models/delete_agent_profile200_response.py +87 -0
  18. asteroid_odyssey/agents_v1_gen/models/error_response.py +87 -0
  19. asteroid_odyssey/agents_v1_gen/models/execution_response.py +87 -0
  20. asteroid_odyssey/agents_v1_gen/models/execution_result.py +101 -0
  21. asteroid_odyssey/agents_v1_gen/models/execution_result_response.py +100 -0
  22. asteroid_odyssey/agents_v1_gen/models/execution_status_response.py +95 -0
  23. asteroid_odyssey/agents_v1_gen/models/health_check200_response.py +87 -0
  24. asteroid_odyssey/agents_v1_gen/models/health_check500_response.py +87 -0
  25. asteroid_odyssey/agents_v1_gen/models/proxy_type.py +37 -0
  26. asteroid_odyssey/agents_v1_gen/models/status.py +43 -0
  27. asteroid_odyssey/agents_v1_gen/models/structured_agent_execution_request.py +89 -0
  28. asteroid_odyssey/agents_v1_gen/models/update_agent_profile_request.py +112 -0
  29. asteroid_odyssey/agents_v1_gen/models/upload_execution_files200_response.py +89 -0
  30. asteroid_odyssey/agents_v1_gen/rest.py +258 -0
  31. asteroid_odyssey/agents_v2_gen/__init__.py +99 -0
  32. asteroid_odyssey/agents_v2_gen/api/__init__.py +5 -0
  33. asteroid_odyssey/agents_v2_gen/api/execution_api.py +625 -0
  34. asteroid_odyssey/agents_v2_gen/api_client.py +801 -0
  35. asteroid_odyssey/agents_v2_gen/api_response.py +21 -0
  36. asteroid_odyssey/agents_v2_gen/configuration.py +612 -0
  37. asteroid_odyssey/agents_v2_gen/exceptions.py +216 -0
  38. asteroid_odyssey/agents_v2_gen/models/__init__.py +42 -0
  39. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_completed.py +100 -0
  40. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_failed.py +100 -0
  41. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_started.py +100 -0
  42. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_generic.py +100 -0
  43. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_status_changed.py +100 -0
  44. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_step_completed.py +100 -0
  45. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_step_started.py +100 -0
  46. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_terminal.py +100 -0
  47. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_transitioned_node.py +100 -0
  48. asteroid_odyssey/agents_v2_gen/models/activity_payload_union_user_message_received.py +100 -0
  49. asteroid_odyssey/agents_v2_gen/models/error.py +89 -0
  50. asteroid_odyssey/agents_v2_gen/models/execution_activity.py +98 -0
  51. asteroid_odyssey/agents_v2_gen/models/execution_activity_action_completed_payload.py +87 -0
  52. asteroid_odyssey/agents_v2_gen/models/execution_activity_action_failed_payload.py +87 -0
  53. asteroid_odyssey/agents_v2_gen/models/execution_activity_action_started_payload.py +87 -0
  54. asteroid_odyssey/agents_v2_gen/models/execution_activity_generic_payload.py +87 -0
  55. asteroid_odyssey/agents_v2_gen/models/execution_activity_payload_union.py +252 -0
  56. asteroid_odyssey/agents_v2_gen/models/execution_activity_status_changed_payload.py +88 -0
  57. asteroid_odyssey/agents_v2_gen/models/execution_activity_step_completed_payload.py +87 -0
  58. asteroid_odyssey/agents_v2_gen/models/execution_activity_step_started_payload.py +87 -0
  59. asteroid_odyssey/agents_v2_gen/models/execution_activity_transitioned_node_payload.py +89 -0
  60. asteroid_odyssey/agents_v2_gen/models/execution_activity_user_message_received_payload.py +89 -0
  61. asteroid_odyssey/agents_v2_gen/models/execution_status.py +43 -0
  62. asteroid_odyssey/agents_v2_gen/models/execution_terminal_payload.py +96 -0
  63. asteroid_odyssey/agents_v2_gen/models/execution_user_messages_add_text_body.py +87 -0
  64. asteroid_odyssey/agents_v2_gen/models/versions.py +37 -0
  65. asteroid_odyssey/agents_v2_gen/rest.py +258 -0
  66. asteroid_odyssey/client.py +881 -304
  67. asteroid_odyssey-0.4.0.dist-info/METADATA +213 -0
  68. asteroid_odyssey-0.4.0.dist-info/RECORD +72 -0
  69. {asteroid_odyssey-0.1.22.dist-info → asteroid_odyssey-0.4.0.dist-info}/WHEEL +1 -1
  70. asteroid_odyssey/api/generated/asteroid_agents_api_client/__init__.py +0 -8
  71. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/__init__.py +0 -1
  72. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/agent/get_agents.py +0 -127
  73. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/api/get_open_api.py +0 -79
  74. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/api/health_check.py +0 -131
  75. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/credentials/__init__.py +0 -0
  76. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/credentials/get_credentials_public_key.py +0 -127
  77. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/__init__.py +0 -0
  78. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/delete_execution.py +0 -156
  79. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_browser_session.py +0 -154
  80. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution.py +0 -154
  81. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution_files.py +0 -159
  82. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_execution_progress.py +0 -159
  83. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/get_executions_for_workflow.py +0 -156
  84. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/execution/update_execution_status.py +0 -115
  85. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/notifications/__init__.py +0 -0
  86. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/notifications/set_slack_channel.py +0 -107
  87. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/optimiser/__init__.py +0 -0
  88. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/optimiser/queue_optimisation_job.py +0 -103
  89. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/__init__.py +0 -0
  90. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/add_workflow_credential.py +0 -111
  91. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/create_workflow.py +0 -174
  92. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/delete_workflow.py +0 -156
  93. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/delete_workflow_credentials.py +0 -96
  94. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/execute_workflow.py +0 -179
  95. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_agent_workflow_executions.py +0 -155
  96. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow.py +0 -154
  97. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow_credentials.py +0 -154
  98. asteroid_odyssey/api/generated/asteroid_agents_api_client/api/workflow/get_workflow_versions.py +0 -159
  99. asteroid_odyssey/api/generated/asteroid_agents_api_client/client.py +0 -268
  100. asteroid_odyssey/api/generated/asteroid_agents_api_client/errors.py +0 -16
  101. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/__init__.py +0 -61
  102. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/agent.py +0 -90
  103. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/browser_session.py +0 -136
  104. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request.py +0 -126
  105. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request_fields.py +0 -48
  106. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/create_workflow_request_provider.py +0 -9
  107. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credential.py +0 -66
  108. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credentials_request.py +0 -72
  109. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/credentials_response.py +0 -80
  110. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_execution_response_200.py +0 -58
  111. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_execution_response_404.py +0 -58
  112. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_workflow_response_200.py +0 -58
  113. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/delete_workflow_response_404.py +0 -58
  114. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution.py +0 -147
  115. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_dynamic_data.py +0 -48
  116. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_result.py +0 -43
  117. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/execution_status.py +0 -89
  118. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/file.py +0 -127
  119. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/health_check_response_200.py +0 -58
  120. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/health_check_response_500.py +0 -58
  121. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/optimisation_request.py +0 -59
  122. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/progress_update.py +0 -77
  123. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/result_schema.py +0 -51
  124. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/slack_channel_request.py +0 -58
  125. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/status.py +0 -13
  126. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow.py +0 -152
  127. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_execution.py +0 -82
  128. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_execution_request.py +0 -48
  129. asteroid_odyssey/api/generated/asteroid_agents_api_client/models/workflow_fields.py +0 -48
  130. asteroid_odyssey/api/generated/asteroid_agents_api_client/py.typed +0 -1
  131. asteroid_odyssey/api/generated/asteroid_agents_api_client/types.py +0 -45
  132. asteroid_odyssey/exceptions.py +0 -15
  133. asteroid_odyssey-0.1.22.dist-info/METADATA +0 -31
  134. asteroid_odyssey-0.1.22.dist-info/RECORD +0 -72
  135. asteroid_odyssey-0.1.22.dist-info/entry_points.txt +0 -2
  136. /asteroid_odyssey/{api/generated/asteroid_agents_api_client/api/agent/__init__.py → agents_v1_gen/py.typed} +0 -0
  137. /asteroid_odyssey/{api/generated/asteroid_agents_api_client/api/api/__init__.py → agents_v2_gen/py.typed} +0 -0
  138. {asteroid_odyssey-0.1.22.dist-info → asteroid_odyssey-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,394 +1,971 @@
1
- from typing import Optional, Dict, Any, List, Union, Callable, Tuple
2
- from uuid import UUID
3
- import logging
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
- from .api.generated.asteroid_agents_api_client.models import (
9
- CreateWorkflowRequest,
10
- WorkflowExecution,
11
- ExecutionStatus,
12
- Execution,
13
- WorkflowExecutionRequest,
14
- Agent,
15
- ResultSchema,
16
- CreateWorkflowRequestFields,
17
- CreateWorkflowRequestProvider
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
- from .api.generated.asteroid_agents_api_client.client import Client as ApiClient
20
- from .api.generated.asteroid_agents_api_client.api.execution.get_execution import sync_detailed as get_execution
21
- from .api.generated.asteroid_agents_api_client.api.agent.get_agents import sync_detailed as asteroid_get_agents
22
- from .api.generated.asteroid_agents_api_client.api.workflow.create_workflow import sync_detailed as create_workflow
23
- from .api.generated.asteroid_agents_api_client.api.workflow.get_agent_workflow_executions import sync_detailed as get_agent_workflow_executions
24
- from .api.generated.asteroid_agents_api_client.api.workflow.execute_workflow import sync_detailed as execute_workflow
25
- logger = logging.getLogger(__name__)
26
-
27
- class ExecutionTerminalState(Enum):
28
- """Terminal states for an execution"""
29
- COMPLETED = "completed"
30
- FAILED = "failed"
31
- CANCELLED = "cancelled"
32
- ERROR = "error"
33
-
34
- agent_name = "iris"
35
-
36
- class ExecutionResult:
37
- """Wrapper class for execution results"""
38
- def __init__(self, execution: Execution):
39
- self.execution_id = execution.id
40
- self.status = execution.status
41
- self.result = execution.result
42
- self.error = execution.error if hasattr(execution, 'error') else None
43
- self.created_at = execution.created_at
44
- self.completed_at = execution.completed_at if hasattr(execution, 'completed_at') else None
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
- A high-level client for interacting with the Asteroid API.
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
- def __init__(
51
- self,
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
- Initialize the Asteroid client.
110
+ Create an API client with the provided API key.
58
111
 
59
112
  Args:
60
- api_key: API key for authentication. If not provided, will look for ASTEROID_API_KEY env var
61
- base_url: Base URL for the API. Defaults to production URL if not specified
62
- verify_ssl: Whether to verify SSL certificates. Defaults to True
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
- self.api_key = api_key or os.getenv("ASTEROID_API_KEY")
65
- if not self.api_key:
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
- self.base_url = base_url or "https://odyssey.asteroid.ai/api/v1"
71
- # Initialize API client
72
- self.client = ApiClient(
73
- base_url=self.base_url,
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
- def get_agents(self) -> List["Agent"]:
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
- Get list of available agents.
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
- List of agent details
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
- result = asteroid_get_agents(client=self.client)
87
- if result.parsed is None:
88
- raise ValueError("No agents were returned from the API")
89
- if not isinstance(result.parsed, list):
90
- raise ValueError(f"The result is not of type list, it is of type: {type(result.parsed)}")
91
- return result.parsed
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
- Create a new workflow for an agent.
169
+ Get the current status for an execution.
105
170
 
106
171
  Args:
107
- workflow_name: Name of the workflow
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
- Workflow ID
175
+ The execution status details
114
176
 
115
- Warning:
116
- Custom result schemas are not fully supported yet and will be added in a future update.
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
- fields = CreateWorkflowRequestFields()
147
- fields.additional_properties = {
148
- "workflow_name": workflow_name,
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 execute_workflow(
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
- Execute an existing workflow.
191
+ Get the final result of an execution.
179
192
 
180
193
  Args:
181
- workflow_id: ID of workflow to execute
182
- execution_params: Parameters for workflow execution
194
+ execution_id: The execution identifier
183
195
 
184
196
  Returns:
185
- Execution ID
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
- # Convert execution_params to WorkflowExecutionRequest using from_dict
189
- request_body = WorkflowExecutionRequest.from_dict(execution_params)
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
- result = execute_workflow(
192
- workflow_id=workflow_id,
193
- body=request_body,
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
- logger.error(f"Failed to execute workflow: {str(e)}")
201
- raise
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 get_workflow_executions(self) -> List[WorkflowExecution]:
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
- Get list of workflow executions.
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
- List of workflow executions
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
- try:
211
- result = get_agent_workflow_executions(
212
- agent_name=agent_name,
213
- client=self.client
214
- ).parsed
215
- if not isinstance(result, List):
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
- def get_execution(self, execution_id: str) -> Execution:
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
- Get the full execution details.
302
+ Upload files to an execution.
225
303
 
226
304
  Args:
227
- execution_id: ID of the execution to retrieve
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
- Execution object with full details
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
- result = get_execution(id=UUID(execution_id), client=self.client).parsed
234
- if not isinstance(result, Execution):
235
- raise ValueError("The result is not of type Execution")
236
- return result
237
- except Exception as e:
238
- logger.error(f"Failed to get execution: {str(e)}")
239
- raise
240
-
241
- def get_execution_status(self, execution_id: str) -> ExecutionStatus:
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 current status of an execution.
371
+ Get the browser session recording URL for a completed execution.
244
372
 
245
373
  Args:
246
- execution_id: ID of the execution to check
374
+ execution_id: The execution identifier
247
375
 
248
376
  Returns:
249
- Current execution status
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
- execution = self.get_execution(execution_id)
252
- if not isinstance(execution.status, ExecutionStatus):
253
- raise ValueError("The execution status is not of type ExecutionStatus")
254
- return execution.status
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 get_execution_result(self, execution_id: str) -> ExecutionResult:
392
+ def get_agent_profiles(self, organization_id: str) -> List[AgentProfile]:
257
393
  """
258
- Get the result of an execution.
394
+ Get a list of agent profiles for a specific organization.
259
395
 
260
396
  Args:
261
- execution_id: ID of the execution to get results for
262
-
397
+ organization_id: The organization identifier (required)
263
398
  Returns:
264
- ExecutionResult object containing status, result, and other metadata
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
- ValueError: If execution doesn't exist or hasn't completed
418
+ Exception: If the agent profile request fails
419
+ Example:
420
+ profile = client.get_agent_profile("profile_id")
268
421
  """
269
- execution = self.get_execution(execution_id)
270
- return ExecutionResult(execution)
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 wait_for_execution(
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
- Wait for an execution to reach a terminal state.
430
+ Create an agent profile with automatic credential encryption.
281
431
 
282
432
  Args:
283
- execution_id: ID of the execution to wait for
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
- Final execution status
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
- TimeoutError: If timeout is reached before execution reaches terminal state
293
- ValueError: If execution_id is invalid
550
+ Exception: If the agent profile deletion fails
551
+ Example:
552
+ response = client.delete_agent_profile("profile_id")
294
553
  """
295
- start_time = time.time()
296
- last_status = None
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
- while True:
299
- # Check if we've exceeded timeout
300
- if timeout and (time.time() - start_time) > timeout:
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
- # Get current status
304
- current_status = self.get_execution_status(execution_id)
564
+ Returns:
565
+ PEM-formatted RSA public key string
305
566
 
306
- # Call status callback if status has changed
307
- if status_callback and current_status != last_status:
308
- status_callback(current_status)
309
- last_status = current_status
567
+ Raises:
568
+ Exception: If the public key request fails
310
569
 
311
- # Check if we've reached a terminal state
312
- if current_status.status.value in [state.value for state in ExecutionTerminalState]:
313
- return current_status
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
- # Wait before next check
316
- time.sleep(polling_interval)
579
+ def __enter__(self):
580
+ """Context manager entry."""
581
+ return self
317
582
 
318
- def wait_for_execution_result(
319
- self,
320
- execution_id: str,
321
- polling_interval: float = 1.0,
322
- timeout: Optional[float] = None,
323
- status_callback: Optional[Callable[[ExecutionStatus], None]] = None
324
- ) -> ExecutionResult:
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
- Wait for an execution to complete and get its result.
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: ID of the execution to wait for
330
- polling_interval: Time in seconds between status checks
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
- ExecutionResult object containing final status, result, and other metadata
336
-
637
+ A list of execution activities
337
638
  Raises:
338
- TimeoutError: If timeout is reached before execution completes
339
- ValueError: If execution_id is invalid
639
+ Exception: If the execution activities request fails
340
640
  """
341
- # Wait for execution to reach terminal state
342
- final_status = self.wait_for_execution(
343
- execution_id=execution_id,
344
- polling_interval=polling_interval,
345
- timeout=timeout,
346
- status_callback=status_callback
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
- # If execution failed, include error information in logs
353
- if final_status in [ExecutionTerminalState.FAILED, ExecutionTerminalState.ERROR]:
354
- logger.error(f"Execution {execution_id} failed with error: {result.error}")
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
- return result
664
+ This is a convenience function that creates an AsteroidClient instance.
357
665
 
358
- def execute_workflow_and_get_result(
359
- self,
360
- workflow_id: UUID,
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
- Args:
370
- workflow_id: ID of workflow to execute
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
- Returns:
377
- ExecutionResult object containing final status, result, and other metadata
378
- """
379
- # Start execution
380
- execution_id = self.execute_workflow(workflow_id, execution_params)
381
-
382
- # Wait for result
383
- return self.wait_for_execution_result(
384
- execution_id=execution_id,
385
- polling_interval=polling_interval,
386
- timeout=timeout,
387
- status_callback=status_callback
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
- def __enter__(self):
391
- return self
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
- def __exit__(self, exc_type, exc_val, exc_tb):
394
- pass
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
+ ]