asteroid-odyssey 1.3.1__py3-none-any.whl → 1.3.3__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.
@@ -7,9 +7,15 @@ from .client import (
7
7
  wait_for_execution_result,
8
8
  upload_execution_files,
9
9
  get_browser_session_recording,
10
+ get_agent_profiles,
11
+ get_agent_profile,
12
+ create_agent_profile,
13
+ update_agent_profile,
14
+ delete_agent_profile,
10
15
  get_last_n_execution_activities,
11
16
  add_message_to_execution,
12
17
  wait_for_agent_interaction,
18
+ get_credentials_public_key,
13
19
  AsteroidAPIError,
14
20
  ExecutionError,
15
21
  TimeoutError,
@@ -26,9 +32,15 @@ __all__ = [
26
32
  'wait_for_execution_result',
27
33
  'upload_execution_files',
28
34
  'get_browser_session_recording',
35
+ 'get_agent_profiles',
36
+ 'get_agent_profile',
37
+ 'create_agent_profile',
38
+ 'update_agent_profile',
39
+ 'delete_agent_profile',
29
40
  'get_last_n_execution_activities',
30
41
  'add_message_to_execution',
31
42
  'wait_for_agent_interaction',
43
+ 'get_credentials_public_key',
32
44
  'AsteroidAPIError',
33
45
  'ExecutionError',
34
46
  'TimeoutError',
@@ -57,10 +57,67 @@ __all__ = [
57
57
  "ExecutionStatus",
58
58
  "ExecutionTerminalPayload",
59
59
  "ExecutionUserMessagesAddTextBody",
60
+ "File",
60
61
  "Versions",
61
62
  ]
62
63
 
63
- # import apis into sdk package
64
+ if __import__("typing").TYPE_CHECKING:
65
+ # import apis into sdk package
66
+ from asteroid_odyssey.agents_v2_gen.api.execution_api import ExecutionApi as ExecutionApi
67
+
68
+ # import ApiClient
69
+ from asteroid_odyssey.agents_v2_gen.api_response import ApiResponse as ApiResponse
70
+ from asteroid_odyssey.agents_v2_gen.api_client import ApiClient as ApiClient
71
+ from asteroid_odyssey.agents_v2_gen.configuration import Configuration as Configuration
72
+ from asteroid_odyssey.agents_v2_gen.exceptions import OpenApiException as OpenApiException
73
+ from asteroid_odyssey.agents_v2_gen.exceptions import ApiTypeError as ApiTypeError
74
+ from asteroid_odyssey.agents_v2_gen.exceptions import ApiValueError as ApiValueError
75
+ from asteroid_odyssey.agents_v2_gen.exceptions import ApiKeyError as ApiKeyError
76
+ from asteroid_odyssey.agents_v2_gen.exceptions import ApiAttributeError as ApiAttributeError
77
+ from asteroid_odyssey.agents_v2_gen.exceptions import ApiException as ApiException
78
+
79
+ # import models into sdk package
80
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_completed import ActivityPayloadUnionActionCompleted as ActivityPayloadUnionActionCompleted
81
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_failed import ActivityPayloadUnionActionFailed as ActivityPayloadUnionActionFailed
82
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_started import ActivityPayloadUnionActionStarted as ActivityPayloadUnionActionStarted
83
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_generic import ActivityPayloadUnionGeneric as ActivityPayloadUnionGeneric
84
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_status_changed import ActivityPayloadUnionStatusChanged as ActivityPayloadUnionStatusChanged
85
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_step_completed import ActivityPayloadUnionStepCompleted as ActivityPayloadUnionStepCompleted
86
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_step_started import ActivityPayloadUnionStepStarted as ActivityPayloadUnionStepStarted
87
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_terminal import ActivityPayloadUnionTerminal as ActivityPayloadUnionTerminal
88
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_transitioned_node import ActivityPayloadUnionTransitionedNode as ActivityPayloadUnionTransitionedNode
89
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_user_message_received import ActivityPayloadUnionUserMessageReceived as ActivityPayloadUnionUserMessageReceived
90
+ from asteroid_odyssey.agents_v2_gen.models.error import Error as Error
91
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity import ExecutionActivity as ExecutionActivity
92
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_completed_payload import ExecutionActivityActionCompletedPayload as ExecutionActivityActionCompletedPayload
93
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_failed_payload import ExecutionActivityActionFailedPayload as ExecutionActivityActionFailedPayload
94
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_started_payload import ExecutionActivityActionStartedPayload as ExecutionActivityActionStartedPayload
95
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_generic_payload import ExecutionActivityGenericPayload as ExecutionActivityGenericPayload
96
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_payload_union import ExecutionActivityPayloadUnion as ExecutionActivityPayloadUnion
97
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_status_changed_payload import ExecutionActivityStatusChangedPayload as ExecutionActivityStatusChangedPayload
98
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_step_completed_payload import ExecutionActivityStepCompletedPayload as ExecutionActivityStepCompletedPayload
99
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_step_started_payload import ExecutionActivityStepStartedPayload as ExecutionActivityStepStartedPayload
100
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_transitioned_node_payload import ExecutionActivityTransitionedNodePayload as ExecutionActivityTransitionedNodePayload
101
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_user_message_received_payload import ExecutionActivityUserMessageReceivedPayload as ExecutionActivityUserMessageReceivedPayload
102
+ from asteroid_odyssey.agents_v2_gen.models.execution_awaiting_confirmation_payload import ExecutionAwaitingConfirmationPayload as ExecutionAwaitingConfirmationPayload
103
+ from asteroid_odyssey.agents_v2_gen.models.execution_completed_payload import ExecutionCompletedPayload as ExecutionCompletedPayload
104
+ from asteroid_odyssey.agents_v2_gen.models.execution_failed_payload import ExecutionFailedPayload as ExecutionFailedPayload
105
+ from asteroid_odyssey.agents_v2_gen.models.execution_paused_payload import ExecutionPausedPayload as ExecutionPausedPayload
106
+ from asteroid_odyssey.agents_v2_gen.models.execution_status import ExecutionStatus as ExecutionStatus
107
+ from asteroid_odyssey.agents_v2_gen.models.execution_terminal_payload import ExecutionTerminalPayload as ExecutionTerminalPayload
108
+ from asteroid_odyssey.agents_v2_gen.models.execution_user_messages_add_text_body import ExecutionUserMessagesAddTextBody as ExecutionUserMessagesAddTextBody
109
+ from asteroid_odyssey.agents_v2_gen.models.file import File as File
110
+ from asteroid_odyssey.agents_v2_gen.models.versions import Versions as Versions
111
+
112
+ else:
113
+ from lazy_imports import LazyModule, as_package, load
114
+
115
+ load(
116
+ LazyModule(
117
+ *as_package(__file__),
118
+ ("__version__", __version__),
119
+ ("__all__", __all__),
120
+ """# import apis into sdk package
64
121
  from asteroid_odyssey.agents_v2_gen.api.execution_api import ExecutionApi as ExecutionApi
65
122
 
66
123
  # import ApiClient
@@ -104,4 +161,11 @@ from asteroid_odyssey.agents_v2_gen.models.execution_paused_payload import Execu
104
161
  from asteroid_odyssey.agents_v2_gen.models.execution_status import ExecutionStatus as ExecutionStatus
105
162
  from asteroid_odyssey.agents_v2_gen.models.execution_terminal_payload import ExecutionTerminalPayload as ExecutionTerminalPayload
106
163
  from asteroid_odyssey.agents_v2_gen.models.execution_user_messages_add_text_body import ExecutionUserMessagesAddTextBody as ExecutionUserMessagesAddTextBody
164
+ from asteroid_odyssey.agents_v2_gen.models.file import File as File
107
165
  from asteroid_odyssey.agents_v2_gen.models.versions import Versions as Versions
166
+
167
+ """,
168
+ name=__name__,
169
+ doc=__doc__,
170
+ )
171
+ )
@@ -1,5 +1,20 @@
1
1
  # flake8: noqa
2
2
 
3
- # import apis into api package
3
+ if __import__("typing").TYPE_CHECKING:
4
+ # import apis into api package
5
+ from asteroid_odyssey.agents_v2_gen.api.execution_api import ExecutionApi
6
+
7
+ else:
8
+ from lazy_imports import LazyModule, as_package, load
9
+
10
+ load(
11
+ LazyModule(
12
+ *as_package(__file__),
13
+ """# import apis into api package
4
14
  from asteroid_odyssey.agents_v2_gen.api.execution_api import ExecutionApi
5
15
 
16
+ """,
17
+ name=__name__,
18
+ doc=__doc__,
19
+ )
20
+ )
@@ -19,8 +19,10 @@ from typing_extensions import Annotated
19
19
  from pydantic import Field, StrictInt, StrictStr, field_validator
20
20
  from typing import List, Optional
21
21
  from typing_extensions import Annotated
22
+ from uuid import UUID
22
23
  from asteroid_odyssey.agents_v2_gen.models.execution_activity import ExecutionActivity
23
24
  from asteroid_odyssey.agents_v2_gen.models.execution_user_messages_add_text_body import ExecutionUserMessagesAddTextBody
25
+ from asteroid_odyssey.agents_v2_gen.models.file import File
24
26
 
25
27
  from asteroid_odyssey.agents_v2_gen.api_client import ApiClient, RequestSerialized
26
28
  from asteroid_odyssey.agents_v2_gen.api_response import ApiResponse
@@ -43,7 +45,7 @@ class ExecutionApi:
43
45
  @validate_call
44
46
  def activities_get(
45
47
  self,
46
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
48
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
47
49
  order: Annotated[Optional[StrictStr], Field(description="Sort order for activities by timestamp")] = None,
48
50
  limit: Annotated[Optional[StrictInt], Field(description="Maximum number of activities to return")] = None,
49
51
  _request_timeout: Union[
@@ -118,7 +120,7 @@ class ExecutionApi:
118
120
  @validate_call
119
121
  def activities_get_with_http_info(
120
122
  self,
121
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
123
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
122
124
  order: Annotated[Optional[StrictStr], Field(description="Sort order for activities by timestamp")] = None,
123
125
  limit: Annotated[Optional[StrictInt], Field(description="Maximum number of activities to return")] = None,
124
126
  _request_timeout: Union[
@@ -193,7 +195,7 @@ class ExecutionApi:
193
195
  @validate_call
194
196
  def activities_get_without_preload_content(
195
197
  self,
196
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
198
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
197
199
  order: Annotated[Optional[StrictStr], Field(description="Sort order for activities by timestamp")] = None,
198
200
  limit: Annotated[Optional[StrictInt], Field(description="Maximum number of activities to return")] = None,
199
201
  _request_timeout: Union[
@@ -335,10 +337,272 @@ class ExecutionApi:
335
337
 
336
338
 
337
339
 
340
+ @validate_call
341
+ def context_files_get(
342
+ self,
343
+ execution_id: UUID,
344
+ _request_timeout: Union[
345
+ None,
346
+ Annotated[StrictFloat, Field(gt=0)],
347
+ Tuple[
348
+ Annotated[StrictFloat, Field(gt=0)],
349
+ Annotated[StrictFloat, Field(gt=0)]
350
+ ]
351
+ ] = None,
352
+ _request_auth: Optional[Dict[StrictStr, Any]] = None,
353
+ _content_type: Optional[StrictStr] = None,
354
+ _headers: Optional[Dict[StrictStr, Any]] = None,
355
+ _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
356
+ ) -> List[File]:
357
+ """context_files_get
358
+
359
+
360
+ :param execution_id: (required)
361
+ :type execution_id: str
362
+ :param _request_timeout: timeout setting for this request. If one
363
+ number provided, it will be total request
364
+ timeout. It can also be a pair (tuple) of
365
+ (connection, read) timeouts.
366
+ :type _request_timeout: int, tuple(int, int), optional
367
+ :param _request_auth: set to override the auth_settings for an a single
368
+ request; this effectively ignores the
369
+ authentication in the spec for a single request.
370
+ :type _request_auth: dict, optional
371
+ :param _content_type: force content-type for the request.
372
+ :type _content_type: str, Optional
373
+ :param _headers: set to override the headers for a single
374
+ request; this effectively ignores the headers
375
+ in the spec for a single request.
376
+ :type _headers: dict, optional
377
+ :param _host_index: set to override the host_index for a single
378
+ request; this effectively ignores the host_index
379
+ in the spec for a single request.
380
+ :type _host_index: int, optional
381
+ :return: Returns the result object.
382
+ """ # noqa: E501
383
+
384
+ _param = self._context_files_get_serialize(
385
+ execution_id=execution_id,
386
+ _request_auth=_request_auth,
387
+ _content_type=_content_type,
388
+ _headers=_headers,
389
+ _host_index=_host_index
390
+ )
391
+
392
+ _response_types_map: Dict[str, Optional[str]] = {
393
+ '200': "List[File]",
394
+ '404': "str",
395
+ }
396
+ response_data = self.api_client.call_api(
397
+ *_param,
398
+ _request_timeout=_request_timeout
399
+ )
400
+ response_data.read()
401
+ return self.api_client.response_deserialize(
402
+ response_data=response_data,
403
+ response_types_map=_response_types_map,
404
+ ).data
405
+
406
+
407
+ @validate_call
408
+ def context_files_get_with_http_info(
409
+ self,
410
+ execution_id: UUID,
411
+ _request_timeout: Union[
412
+ None,
413
+ Annotated[StrictFloat, Field(gt=0)],
414
+ Tuple[
415
+ Annotated[StrictFloat, Field(gt=0)],
416
+ Annotated[StrictFloat, Field(gt=0)]
417
+ ]
418
+ ] = None,
419
+ _request_auth: Optional[Dict[StrictStr, Any]] = None,
420
+ _content_type: Optional[StrictStr] = None,
421
+ _headers: Optional[Dict[StrictStr, Any]] = None,
422
+ _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
423
+ ) -> ApiResponse[List[File]]:
424
+ """context_files_get
425
+
426
+
427
+ :param execution_id: (required)
428
+ :type execution_id: str
429
+ :param _request_timeout: timeout setting for this request. If one
430
+ number provided, it will be total request
431
+ timeout. It can also be a pair (tuple) of
432
+ (connection, read) timeouts.
433
+ :type _request_timeout: int, tuple(int, int), optional
434
+ :param _request_auth: set to override the auth_settings for an a single
435
+ request; this effectively ignores the
436
+ authentication in the spec for a single request.
437
+ :type _request_auth: dict, optional
438
+ :param _content_type: force content-type for the request.
439
+ :type _content_type: str, Optional
440
+ :param _headers: set to override the headers for a single
441
+ request; this effectively ignores the headers
442
+ in the spec for a single request.
443
+ :type _headers: dict, optional
444
+ :param _host_index: set to override the host_index for a single
445
+ request; this effectively ignores the host_index
446
+ in the spec for a single request.
447
+ :type _host_index: int, optional
448
+ :return: Returns the result object.
449
+ """ # noqa: E501
450
+
451
+ _param = self._context_files_get_serialize(
452
+ execution_id=execution_id,
453
+ _request_auth=_request_auth,
454
+ _content_type=_content_type,
455
+ _headers=_headers,
456
+ _host_index=_host_index
457
+ )
458
+
459
+ _response_types_map: Dict[str, Optional[str]] = {
460
+ '200': "List[File]",
461
+ '404': "str",
462
+ }
463
+ response_data = self.api_client.call_api(
464
+ *_param,
465
+ _request_timeout=_request_timeout
466
+ )
467
+ response_data.read()
468
+ return self.api_client.response_deserialize(
469
+ response_data=response_data,
470
+ response_types_map=_response_types_map,
471
+ )
472
+
473
+
474
+ @validate_call
475
+ def context_files_get_without_preload_content(
476
+ self,
477
+ execution_id: UUID,
478
+ _request_timeout: Union[
479
+ None,
480
+ Annotated[StrictFloat, Field(gt=0)],
481
+ Tuple[
482
+ Annotated[StrictFloat, Field(gt=0)],
483
+ Annotated[StrictFloat, Field(gt=0)]
484
+ ]
485
+ ] = None,
486
+ _request_auth: Optional[Dict[StrictStr, Any]] = None,
487
+ _content_type: Optional[StrictStr] = None,
488
+ _headers: Optional[Dict[StrictStr, Any]] = None,
489
+ _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
490
+ ) -> RESTResponseType:
491
+ """context_files_get
492
+
493
+
494
+ :param execution_id: (required)
495
+ :type execution_id: str
496
+ :param _request_timeout: timeout setting for this request. If one
497
+ number provided, it will be total request
498
+ timeout. It can also be a pair (tuple) of
499
+ (connection, read) timeouts.
500
+ :type _request_timeout: int, tuple(int, int), optional
501
+ :param _request_auth: set to override the auth_settings for an a single
502
+ request; this effectively ignores the
503
+ authentication in the spec for a single request.
504
+ :type _request_auth: dict, optional
505
+ :param _content_type: force content-type for the request.
506
+ :type _content_type: str, Optional
507
+ :param _headers: set to override the headers for a single
508
+ request; this effectively ignores the headers
509
+ in the spec for a single request.
510
+ :type _headers: dict, optional
511
+ :param _host_index: set to override the host_index for a single
512
+ request; this effectively ignores the host_index
513
+ in the spec for a single request.
514
+ :type _host_index: int, optional
515
+ :return: Returns the result object.
516
+ """ # noqa: E501
517
+
518
+ _param = self._context_files_get_serialize(
519
+ execution_id=execution_id,
520
+ _request_auth=_request_auth,
521
+ _content_type=_content_type,
522
+ _headers=_headers,
523
+ _host_index=_host_index
524
+ )
525
+
526
+ _response_types_map: Dict[str, Optional[str]] = {
527
+ '200': "List[File]",
528
+ '404': "str",
529
+ }
530
+ response_data = self.api_client.call_api(
531
+ *_param,
532
+ _request_timeout=_request_timeout
533
+ )
534
+ return response_data.response
535
+
536
+
537
+ def _context_files_get_serialize(
538
+ self,
539
+ execution_id,
540
+ _request_auth,
541
+ _content_type,
542
+ _headers,
543
+ _host_index,
544
+ ) -> RequestSerialized:
545
+
546
+ _host = None
547
+
548
+ _collection_formats: Dict[str, str] = {
549
+ }
550
+
551
+ _path_params: Dict[str, str] = {}
552
+ _query_params: List[Tuple[str, str]] = []
553
+ _header_params: Dict[str, Optional[str]] = _headers or {}
554
+ _form_params: List[Tuple[str, str]] = []
555
+ _files: Dict[
556
+ str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
557
+ ] = {}
558
+ _body_params: Optional[bytes] = None
559
+
560
+ # process the path parameters
561
+ if execution_id is not None:
562
+ _path_params['executionId'] = execution_id
563
+ # process the query parameters
564
+ # process the header parameters
565
+ # process the form parameters
566
+ # process the body parameter
567
+
568
+
569
+ # set the HTTP header `Accept`
570
+ if 'Accept' not in _header_params:
571
+ _header_params['Accept'] = self.api_client.select_header_accept(
572
+ [
573
+ 'application/json',
574
+ 'text/plain'
575
+ ]
576
+ )
577
+
578
+
579
+ # authentication setting
580
+ _auth_settings: List[str] = [
581
+ 'ApiKeyAuth'
582
+ ]
583
+
584
+ return self.api_client.param_serialize(
585
+ method='GET',
586
+ resource_path='/executions/{executionId}/context-files',
587
+ path_params=_path_params,
588
+ query_params=_query_params,
589
+ header_params=_header_params,
590
+ body=_body_params,
591
+ post_params=_form_params,
592
+ files=_files,
593
+ auth_settings=_auth_settings,
594
+ collection_formats=_collection_formats,
595
+ _host=_host,
596
+ _request_auth=_request_auth
597
+ )
598
+
599
+
600
+
601
+
338
602
  @validate_call
339
603
  def user_messages_add(
340
604
  self,
341
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
605
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
342
606
  execution_user_messages_add_text_body: Annotated[ExecutionUserMessagesAddTextBody, Field(description="The message content to send")],
343
607
  _request_timeout: Union[
344
608
  None,
@@ -409,7 +673,7 @@ class ExecutionApi:
409
673
  @validate_call
410
674
  def user_messages_add_with_http_info(
411
675
  self,
412
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
676
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
413
677
  execution_user_messages_add_text_body: Annotated[ExecutionUserMessagesAddTextBody, Field(description="The message content to send")],
414
678
  _request_timeout: Union[
415
679
  None,
@@ -480,7 +744,7 @@ class ExecutionApi:
480
744
  @validate_call
481
745
  def user_messages_add_without_preload_content(
482
746
  self,
483
- execution_id: Annotated[StrictStr, Field(description="The unique identifier of the execution")],
747
+ execution_id: Annotated[UUID, Field(description="The unique identifier of the execution")],
484
748
  execution_user_messages_add_text_body: Annotated[ExecutionUserMessagesAddTextBody, Field(description="The message content to send")],
485
749
  _request_timeout: Union[
486
750
  None,
@@ -21,6 +21,7 @@ import mimetypes
21
21
  import os
22
22
  import re
23
23
  import tempfile
24
+ import uuid
24
25
 
25
26
  from urllib.parse import quote
26
27
  from typing import Tuple, Optional, List, Dict, Union
@@ -356,6 +357,8 @@ class ApiClient:
356
357
  return obj.get_secret_value()
357
358
  elif isinstance(obj, self.PRIMITIVE_TYPES):
358
359
  return obj
360
+ elif isinstance(obj, uuid.UUID):
361
+ return str(obj)
359
362
  elif isinstance(obj, list):
360
363
  return [
361
364
  self.sanitize_for_serialization(sub_obj) for sub_obj in obj
@@ -408,7 +411,7 @@ class ApiClient:
408
411
  data = json.loads(response_text)
409
412
  except ValueError:
410
413
  data = response_text
411
- elif re.match(r'^application/(json|[\w!#$&.+-^_]+\+json)\s*(;|$)', content_type, re.IGNORECASE):
414
+ elif re.match(r'^application/(json|[\w!#$&.+\-^_]+\+json)\s*(;|$)', content_type, re.IGNORECASE):
412
415
  if response_text == "":
413
416
  data = ""
414
417
  else:
@@ -13,7 +13,47 @@
13
13
  """ # noqa: E501
14
14
 
15
15
 
16
- # import models into model package
16
+ if __import__("typing").TYPE_CHECKING:
17
+ # import models into model package
18
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_completed import ActivityPayloadUnionActionCompleted
19
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_failed import ActivityPayloadUnionActionFailed
20
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_started import ActivityPayloadUnionActionStarted
21
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_generic import ActivityPayloadUnionGeneric
22
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_status_changed import ActivityPayloadUnionStatusChanged
23
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_step_completed import ActivityPayloadUnionStepCompleted
24
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_step_started import ActivityPayloadUnionStepStarted
25
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_terminal import ActivityPayloadUnionTerminal
26
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_transitioned_node import ActivityPayloadUnionTransitionedNode
27
+ from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_user_message_received import ActivityPayloadUnionUserMessageReceived
28
+ from asteroid_odyssey.agents_v2_gen.models.error import Error
29
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity import ExecutionActivity
30
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_completed_payload import ExecutionActivityActionCompletedPayload
31
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_failed_payload import ExecutionActivityActionFailedPayload
32
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_action_started_payload import ExecutionActivityActionStartedPayload
33
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_generic_payload import ExecutionActivityGenericPayload
34
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_payload_union import ExecutionActivityPayloadUnion
35
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_status_changed_payload import ExecutionActivityStatusChangedPayload
36
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_step_completed_payload import ExecutionActivityStepCompletedPayload
37
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_step_started_payload import ExecutionActivityStepStartedPayload
38
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_transitioned_node_payload import ExecutionActivityTransitionedNodePayload
39
+ from asteroid_odyssey.agents_v2_gen.models.execution_activity_user_message_received_payload import ExecutionActivityUserMessageReceivedPayload
40
+ from asteroid_odyssey.agents_v2_gen.models.execution_awaiting_confirmation_payload import ExecutionAwaitingConfirmationPayload
41
+ from asteroid_odyssey.agents_v2_gen.models.execution_completed_payload import ExecutionCompletedPayload
42
+ from asteroid_odyssey.agents_v2_gen.models.execution_failed_payload import ExecutionFailedPayload
43
+ from asteroid_odyssey.agents_v2_gen.models.execution_paused_payload import ExecutionPausedPayload
44
+ from asteroid_odyssey.agents_v2_gen.models.execution_status import ExecutionStatus
45
+ from asteroid_odyssey.agents_v2_gen.models.execution_terminal_payload import ExecutionTerminalPayload
46
+ from asteroid_odyssey.agents_v2_gen.models.execution_user_messages_add_text_body import ExecutionUserMessagesAddTextBody
47
+ from asteroid_odyssey.agents_v2_gen.models.file import File
48
+ from asteroid_odyssey.agents_v2_gen.models.versions import Versions
49
+
50
+ else:
51
+ from lazy_imports import LazyModule, as_package, load
52
+
53
+ load(
54
+ LazyModule(
55
+ *as_package(__file__),
56
+ """# import models into model package
17
57
  from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_completed import ActivityPayloadUnionActionCompleted
18
58
  from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_failed import ActivityPayloadUnionActionFailed
19
59
  from asteroid_odyssey.agents_v2_gen.models.activity_payload_union_action_started import ActivityPayloadUnionActionStarted
@@ -43,4 +83,11 @@ from asteroid_odyssey.agents_v2_gen.models.execution_paused_payload import Execu
43
83
  from asteroid_odyssey.agents_v2_gen.models.execution_status import ExecutionStatus
44
84
  from asteroid_odyssey.agents_v2_gen.models.execution_terminal_payload import ExecutionTerminalPayload
45
85
  from asteroid_odyssey.agents_v2_gen.models.execution_user_messages_add_text_body import ExecutionUserMessagesAddTextBody
86
+ from asteroid_odyssey.agents_v2_gen.models.file import File
46
87
  from asteroid_odyssey.agents_v2_gen.models.versions import Versions
88
+
89
+ """,
90
+ name=__name__,
91
+ doc=__doc__,
92
+ )
93
+ )
@@ -18,8 +18,9 @@ import re # noqa: F401
18
18
  import json
19
19
 
20
20
  from datetime import datetime
21
- from pydantic import BaseModel, ConfigDict, Field, StrictStr
21
+ from pydantic import BaseModel, ConfigDict, Field
22
22
  from typing import Any, ClassVar, Dict, List
23
+ from uuid import UUID
23
24
  from asteroid_odyssey.agents_v2_gen.models.execution_activity_payload_union import ExecutionActivityPayloadUnion
24
25
  from typing import Optional, Set
25
26
  from typing_extensions import Self
@@ -28,8 +29,8 @@ class ExecutionActivity(BaseModel):
28
29
  """
29
30
  ExecutionActivity
30
31
  """ # noqa: E501
31
- execution_id: StrictStr = Field(alias="executionId")
32
- id: StrictStr
32
+ execution_id: UUID = Field(alias="executionId")
33
+ id: UUID
33
34
  payload: ExecutionActivityPayloadUnion
34
35
  timestamp: datetime
35
36
  __properties: ClassVar[List[str]] = ["executionId", "id", "payload", "timestamp"]
@@ -19,6 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, Field, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List
22
+ from uuid import UUID
22
23
  from typing import Optional, Set
23
24
  from typing_extensions import Self
24
25
 
@@ -27,7 +28,7 @@ class ExecutionActivityTransitionedNodePayload(BaseModel):
27
28
  ExecutionActivityTransitionedNodePayload
28
29
  """ # noqa: E501
29
30
  new_node_name: StrictStr = Field(alias="newNodeName")
30
- new_node_uuid: StrictStr = Field(alias="newNodeUUID")
31
+ new_node_uuid: UUID = Field(alias="newNodeUUID")
31
32
  __properties: ClassVar[List[str]] = ["newNodeName", "newNodeUUID"]
32
33
 
33
34
  model_config = ConfigDict(
@@ -19,6 +19,7 @@ import json
19
19
 
20
20
  from pydantic import BaseModel, ConfigDict, Field, StrictStr
21
21
  from typing import Any, ClassVar, Dict, List
22
+ from uuid import UUID
22
23
  from typing import Optional, Set
23
24
  from typing_extensions import Self
24
25
 
@@ -27,7 +28,7 @@ class ExecutionActivityUserMessageReceivedPayload(BaseModel):
27
28
  ExecutionActivityUserMessageReceivedPayload
28
29
  """ # noqa: E501
29
30
  message: StrictStr
30
- user_uuid: StrictStr = Field(alias="userUUID")
31
+ user_uuid: UUID = Field(alias="userUUID")
31
32
  __properties: ClassVar[List[str]] = ["message", "userUUID"]
32
33
 
33
34
  model_config = ConfigDict(
@@ -0,0 +1,109 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Agent Service
5
+
6
+ No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
7
+
8
+ The version of the OpenAPI document: v2
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import pprint
17
+ import re # noqa: F401
18
+ import json
19
+
20
+ from datetime import datetime
21
+ from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
22
+ from typing import Any, ClassVar, Dict, List
23
+ from uuid import UUID
24
+ from typing import Optional, Set
25
+ from typing_extensions import Self
26
+
27
+ class File(BaseModel):
28
+ """
29
+ File
30
+ """ # noqa: E501
31
+ agent_id: UUID = Field(alias="agentId")
32
+ created_at: datetime = Field(alias="createdAt")
33
+ execution_id: UUID = Field(alias="executionId")
34
+ file_ext: StrictStr = Field(alias="fileExt")
35
+ file_name: StrictStr = Field(alias="fileName")
36
+ file_path: StrictStr = Field(alias="filePath")
37
+ file_size: StrictInt = Field(alias="fileSize")
38
+ file_type: StrictStr = Field(alias="fileType")
39
+ id: UUID
40
+ mime_type: StrictStr = Field(alias="mimeType")
41
+ signed_url: StrictStr = Field(alias="signedUrl")
42
+ __properties: ClassVar[List[str]] = ["agentId", "createdAt", "executionId", "fileExt", "fileName", "filePath", "fileSize", "fileType", "id", "mimeType", "signedUrl"]
43
+
44
+ model_config = ConfigDict(
45
+ populate_by_name=True,
46
+ validate_assignment=True,
47
+ protected_namespaces=(),
48
+ )
49
+
50
+
51
+ def to_str(self) -> str:
52
+ """Returns the string representation of the model using alias"""
53
+ return pprint.pformat(self.model_dump(by_alias=True))
54
+
55
+ def to_json(self) -> str:
56
+ """Returns the JSON representation of the model using alias"""
57
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
58
+ return json.dumps(self.to_dict())
59
+
60
+ @classmethod
61
+ def from_json(cls, json_str: str) -> Optional[Self]:
62
+ """Create an instance of File from a JSON string"""
63
+ return cls.from_dict(json.loads(json_str))
64
+
65
+ def to_dict(self) -> Dict[str, Any]:
66
+ """Return the dictionary representation of the model using alias.
67
+
68
+ This has the following differences from calling pydantic's
69
+ `self.model_dump(by_alias=True)`:
70
+
71
+ * `None` is only added to the output dict for nullable fields that
72
+ were set at model initialization. Other fields with value `None`
73
+ are ignored.
74
+ """
75
+ excluded_fields: Set[str] = set([
76
+ ])
77
+
78
+ _dict = self.model_dump(
79
+ by_alias=True,
80
+ exclude=excluded_fields,
81
+ exclude_none=True,
82
+ )
83
+ return _dict
84
+
85
+ @classmethod
86
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
87
+ """Create an instance of File from a dict"""
88
+ if obj is None:
89
+ return None
90
+
91
+ if not isinstance(obj, dict):
92
+ return cls.model_validate(obj)
93
+
94
+ _obj = cls.model_validate({
95
+ "agentId": obj.get("agentId"),
96
+ "createdAt": obj.get("createdAt"),
97
+ "executionId": obj.get("executionId"),
98
+ "fileExt": obj.get("fileExt"),
99
+ "fileName": obj.get("fileName"),
100
+ "filePath": obj.get("filePath"),
101
+ "fileSize": obj.get("fileSize"),
102
+ "fileType": obj.get("fileType"),
103
+ "id": obj.get("id"),
104
+ "mimeType": obj.get("mimeType"),
105
+ "signedUrl": obj.get("signedUrl")
106
+ })
107
+ return _obj
108
+
109
+
@@ -11,6 +11,8 @@ without modifying any generated files.
11
11
  import time
12
12
  import os
13
13
  import base64
14
+ import requests
15
+ from pathlib import Path
14
16
  from typing import Dict, Any, Optional, List, Union, Tuple, NamedTuple
15
17
  from cryptography.hazmat.primitives import serialization
16
18
  from cryptography.hazmat.primitives.asymmetric import padding, rsa
@@ -38,6 +40,7 @@ from .agents_v2_gen import (
38
40
  ExecutionApi as AgentsV2ExecutionApi,
39
41
  ExecutionActivity,
40
42
  ExecutionUserMessagesAddTextBody,
43
+ File,
41
44
  )
42
45
 
43
46
 
@@ -219,7 +222,7 @@ class AsteroidClient:
219
222
  # Handle case where execution_result might be None or have invalid data
220
223
  if response.execution_result is None:
221
224
  raise AsteroidAPIError("Execution result is not available yet")
222
-
225
+
223
226
  return response.execution_result
224
227
  except ApiException as e:
225
228
  raise AsteroidAPIError(f"Failed to get execution result: {e}") from e
@@ -591,39 +594,39 @@ class AsteroidClient:
591
594
  ) -> AgentInteractionResult:
592
595
  """
593
596
  Wait for an agent interaction request or terminal state.
594
-
597
+
595
598
  This method polls an existing execution until it either:
596
599
  1. Requests human input (paused_by_agent state)
597
600
  2. Reaches a terminal state (completed, failed, cancelled)
598
601
  3. Times out
599
-
602
+
600
603
  Unlike interactive_agent, this method doesn't start an execution or handle
601
604
  the response automatically - it just waits and reports what happened.
602
-
605
+
603
606
  Args:
604
607
  execution_id: The execution identifier for an already started execution
605
608
  poll_interval: How often to check for updates in seconds (default: 2.0)
606
609
  timeout: Maximum wait time in seconds (default: 3600 - 1 hour)
607
-
610
+
608
611
  Returns:
609
612
  AgentInteractionResult containing:
610
613
  - is_terminal: True if execution finished (completed/failed/cancelled)
611
614
  - status: Current execution status string
612
615
  - agent_message: Agent's message if requesting interaction (None if terminal)
613
616
  - execution_result: Final result if terminal state (None if requesting interaction)
614
-
617
+
615
618
  Raises:
616
619
  ValueError: If interval or timeout parameters are invalid
617
620
  TimeoutError: If the execution times out
618
621
  AsteroidAPIError: If API calls fail
619
-
622
+
620
623
  Example:
621
624
  # Start an execution first
622
625
  execution_id = client.execute_agent('agent-id', {'input': 'test'})
623
-
626
+
624
627
  # Wait for interaction or completion
625
628
  result = client.wait_for_agent_interaction(execution_id)
626
-
629
+
627
630
  if result.is_terminal:
628
631
  print(f"Execution finished with status: {result.status}")
629
632
  if result.execution_result:
@@ -640,19 +643,19 @@ class AsteroidClient:
640
643
  raise ValueError("poll_interval must be positive")
641
644
  if timeout <= 0:
642
645
  raise ValueError("timeout must be positive")
643
-
646
+
644
647
  start_time = time.time()
645
-
648
+
646
649
  while True:
647
650
  elapsed_time = time.time() - start_time
648
651
  if elapsed_time >= timeout:
649
652
  raise TimeoutError(f"Wait for interaction on execution {execution_id} timed out after {timeout}s")
650
-
653
+
651
654
  # Get current status
652
655
  status_response = self.get_execution_status(execution_id)
653
656
  current_status = status_response.status
654
657
  status_str = current_status.value.lower()
655
-
658
+
656
659
  # Handle terminal states
657
660
  if current_status == Status.COMPLETED:
658
661
  try:
@@ -668,7 +671,7 @@ class AsteroidClient:
668
671
  time.sleep(poll_interval)
669
672
  continue
670
673
  raise e
671
-
674
+
672
675
  elif current_status in [Status.FAILED, Status.CANCELLED]:
673
676
  try:
674
677
  execution_result = self.get_execution_result(execution_id)
@@ -686,7 +689,7 @@ class AsteroidClient:
686
689
  agent_message=None,
687
690
  execution_result=None
688
691
  )
689
-
692
+
690
693
  # Handle agent interaction request
691
694
  elif current_status == Status.PAUSED_BY_AGENT:
692
695
  # Get the agent's message/request
@@ -697,34 +700,34 @@ class AsteroidClient:
697
700
  agent_message=agent_message,
698
701
  execution_result=None
699
702
  )
700
-
703
+
701
704
  # Wait before next poll for non-terminal, non-interaction states
702
705
  time.sleep(poll_interval)
703
-
706
+
704
707
  def _extract_agent_request_message(self, execution_id: str) -> str:
705
708
  """
706
709
  Extract the agent's request message from recent activities.
707
-
710
+
708
711
  Args:
709
712
  execution_id: The execution identifier
710
-
713
+
711
714
  Returns:
712
715
  The agent's message or a default message if not found
713
716
  """
714
717
  try:
715
718
  activities = self.get_last_n_execution_activities(execution_id, 20)
716
-
719
+
717
720
  # Filter for human input requests
718
721
  human_input_requests = [
719
722
  activity for activity in activities
720
- if (hasattr(activity, 'payload') and
721
- activity.payload and
723
+ if (hasattr(activity, 'payload') and
724
+ activity.payload and
722
725
  getattr(activity.payload, 'activityType', None) == 'action_started')
723
726
  ]
724
-
727
+
725
728
  if human_input_requests:
726
729
  human_input_request = human_input_requests[0]
727
-
730
+
728
731
  # Extract message from payload data with robust error handling
729
732
  try:
730
733
  payload = human_input_request.payload
@@ -735,9 +738,9 @@ class AsteroidClient:
735
738
  return 'Agent is requesting input'
736
739
  except (AttributeError, TypeError) as e:
737
740
  return 'Agent is requesting input (extraction failed)'
738
-
741
+
739
742
  return 'Agent is requesting input'
740
-
743
+
741
744
  except AsteroidAPIError as e:
742
745
  return 'Agent is requesting input (API error)'
743
746
  except Exception as e:
@@ -762,33 +765,6 @@ class AsteroidClient:
762
765
  pass
763
766
  return False
764
767
 
765
- # Utility methods for nicer response formatting
766
- def format_agent_profile(self, profile: AgentProfile) -> str:
767
- """Format an agent profile for nice display."""
768
- credentials_info = f"{[(credential.name, credential.id) for credential in profile.credentials]}" if profile.credentials else "No credentials"
769
- return f"""Agent Profile:
770
- ID: {profile.id}
771
- Name: {profile.name}
772
- Description: {profile.description}
773
- Organization: {profile.organization_id}
774
- Proxy: {profile.proxy_cc.value} ({profile.proxy_type.value})
775
- Captcha Solver: {'Enabled' if profile.captcha_solver_active else 'Disabled'}
776
- Sticky IP: {'Yes' if profile.sticky_ip else 'No'}
777
- Credentials: {credentials_info}
778
- Created: {profile.created_at.strftime('%Y-%m-%d %H:%M:%S')}
779
- Updated: {profile.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"""
780
-
781
- def format_agent_profiles_list(self, profiles: List[AgentProfile]) -> str:
782
- """Format a list of agent profiles for nice display."""
783
- if not profiles:
784
- return "No agent profiles found."
785
-
786
- result = f"Found {len(profiles)} agent profile(s):\n"
787
- for i, profile in enumerate(profiles, 1):
788
- credentials_count = len(profile.credentials) if profile.credentials else 0
789
- result += f" {i}. {profile.name} (ID: {profile.id[:8]}...) - {credentials_count} credentials\n"
790
- return result.rstrip()
791
-
792
768
  # --- V2 ---
793
769
 
794
770
  def get_last_n_execution_activities(self, execution_id: str, n: int) -> List[ExecutionActivity]:
@@ -819,6 +795,123 @@ class AsteroidClient:
819
795
  message_body = ExecutionUserMessagesAddTextBody(message=message)
820
796
  return self.agents_v2_execution_api.user_messages_add(execution_id, message_body)
821
797
 
798
+ def get_execution_files(self, execution_id: str) -> List[File]:
799
+ """
800
+ Get a list of files associated with an execution.
801
+ Args:
802
+ execution_id: The execution identifier
803
+ Returns:
804
+ A list of files associated with the execution
805
+ Raises:
806
+ Exception: If the files request fails
807
+ Example:
808
+ files = client.get_execution_files("execution_id")
809
+ for file in files:
810
+ print(f"File: {file.file_name}, Size: {file.file_size}")
811
+ """
812
+ try:
813
+ return self.agents_v2_execution_api.context_files_get(execution_id)
814
+ except ApiException as e:
815
+ raise AsteroidAPIError(f"Failed to get execution files: {e}") from e
816
+
817
+ def download_execution_file(self, file: File, download_path: Union[str, Path],
818
+ create_dirs: bool = True, timeout: int = 30) -> str:
819
+ """
820
+ Download a file from an execution using its signed URL.
821
+
822
+ Args:
823
+ file: The File object containing the signed URL and metadata
824
+ download_path: Path where the file should be saved. Can be a directory or full file path
825
+ create_dirs: Whether to create parent directories if they don't exist (default: True)
826
+ timeout: Request timeout in seconds (default: 30)
827
+
828
+ Returns:
829
+ The full path where the file was saved
830
+
831
+ Raises:
832
+ AsteroidAPIError: If the download fails
833
+ FileNotFoundError: If the parent directory doesn't exist and create_dirs is False
834
+
835
+ Example:
836
+ files = client.get_execution_files("execution_id")
837
+ for file in files:
838
+ # Download to specific directory
839
+ saved_path = client.download_execution_file(file, "/path/to/downloads/")
840
+ print(f"Downloaded {file.file_name} to {saved_path}")
841
+
842
+ # Download with specific filename
843
+ saved_path = client.download_execution_file(file, "/path/to/downloads/my_file.txt")
844
+ print(f"Downloaded to {saved_path}")
845
+ """
846
+ final_path = None
847
+ try:
848
+ # Convert to Path object for easier manipulation
849
+ download_path = Path(download_path)
850
+
851
+ # Determine the final file path
852
+ if download_path.is_dir() or str(download_path).endswith('/'):
853
+ # If download_path is a directory, use the original filename
854
+ final_path = download_path / file.file_name
855
+ else:
856
+ # If download_path includes a filename, use it as-is
857
+ final_path = download_path
858
+
859
+ # Create parent directories if needed
860
+ if create_dirs:
861
+ final_path.parent.mkdir(parents=True, exist_ok=True)
862
+ elif not final_path.parent.exists():
863
+ raise FileNotFoundError(f"Parent directory does not exist: {final_path.parent}")
864
+
865
+ # Download the file using the signed URL
866
+ response = requests.get(file.signed_url, timeout=timeout, stream=True)
867
+ response.raise_for_status()
868
+
869
+ # Verify content length if available
870
+ expected_size = file.file_size
871
+ content_length = response.headers.get('content-length')
872
+ if content_length and int(content_length) != expected_size:
873
+ raise AsteroidAPIError(
874
+ f"Content length mismatch: expected {expected_size}, got {content_length}"
875
+ )
876
+
877
+ # Write the file in chunks to handle large files efficiently
878
+ chunk_size = 8192
879
+ total_size = 0
880
+
881
+ with open(final_path, 'wb') as f:
882
+ for chunk in response.iter_content(chunk_size=chunk_size):
883
+ if chunk: # Filter out keep-alive chunks
884
+ f.write(chunk)
885
+ total_size += len(chunk)
886
+
887
+ # Final verification of the downloaded file size
888
+ if total_size != expected_size:
889
+ raise AsteroidAPIError(
890
+ f"Downloaded file size mismatch: expected {expected_size}, got {total_size}"
891
+ )
892
+
893
+ return str(final_path)
894
+
895
+ except requests.exceptions.RequestException as e:
896
+ # Clean up partial file on network error
897
+ if final_path and final_path.exists():
898
+ final_path.unlink(missing_ok=True)
899
+ raise AsteroidAPIError(f"Failed to download file {file.file_name}: {e}") from e
900
+ except OSError as e:
901
+ # Clean up partial file on I/O error
902
+ if final_path and final_path.exists():
903
+ final_path.unlink(missing_ok=True)
904
+ raise AsteroidAPIError(f"Failed to save file {file.file_name}: {e}") from e
905
+ except AsteroidAPIError:
906
+ # Clean up partial file on size mismatch or other API errors
907
+ if final_path and final_path.exists():
908
+ final_path.unlink(missing_ok=True)
909
+ raise
910
+ except Exception as e:
911
+ # Clean up partial file on unexpected error
912
+ if final_path and final_path.exists():
913
+ final_path.unlink(missing_ok=True)
914
+ raise AsteroidAPIError(f"Unexpected error downloading file {file.file_name}: {e}") from e
822
915
 
823
916
  # Convenience functions that mirror the TypeScript SDK pattern
824
917
  def create_client(api_key: str, base_url: Optional[str] = None) -> AsteroidClient:
@@ -1113,6 +1206,56 @@ def add_message_to_execution(client: AsteroidClient, execution_id: str, message:
1113
1206
  """
1114
1207
  return client.add_message_to_execution(execution_id, message)
1115
1208
 
1209
+ def get_execution_files(client: AsteroidClient, execution_id: str) -> List[File]:
1210
+ """
1211
+ Get a list of files associated with an execution.
1212
+ Args:
1213
+ client: The AsteroidClient instance
1214
+ execution_id: The execution identifier
1215
+ Returns:
1216
+ A list of files associated with the execution
1217
+ Raises:
1218
+ Exception: If the files request fails
1219
+ Example:
1220
+ files = get_execution_files(client, "execution_id")
1221
+ for file in files:
1222
+ print(f"File: {file.file_name}, Size: {file.file_size}")
1223
+ """
1224
+ return client.get_execution_files(execution_id)
1225
+
1226
+ def download_execution_file(client: AsteroidClient, file: File, download_path: Union[str, Path],
1227
+ create_dirs: bool = True, timeout: int = 30) -> str:
1228
+ """
1229
+ Download a file from an execution using its signed URL.
1230
+
1231
+ Args:
1232
+ client: The AsteroidClient instance
1233
+ file: The File object containing the signed URL and metadata
1234
+ download_path: Path where the file should be saved. Can be a directory or full file path
1235
+ create_dirs: Whether to create parent directories if they don't exist (default: True)
1236
+ timeout: Request timeout in seconds (default: 30)
1237
+
1238
+ Returns:
1239
+ The full path where the file was saved
1240
+
1241
+ Raises:
1242
+ AsteroidAPIError: If the download fails
1243
+ FileNotFoundError: If the parent directory doesn't exist and create_dirs is False
1244
+
1245
+ Example:
1246
+ files = get_execution_files(client, "execution_id")
1247
+ for file in files:
1248
+ # Download to specific directory
1249
+ saved_path = download_execution_file(client, file, "/path/to/downloads/")
1250
+ print(f"Downloaded {file.file_name} to {saved_path}")
1251
+
1252
+ # Download with specific filename
1253
+ saved_path = download_execution_file(client, file, "/path/to/downloads/my_file.txt")
1254
+ print(f"Downloaded to {saved_path}")
1255
+ """
1256
+ return client.download_execution_file(file, download_path, create_dirs, timeout)
1257
+
1258
+
1116
1259
 
1117
1260
  def wait_for_agent_interaction(
1118
1261
  client: AsteroidClient,
@@ -1122,34 +1265,34 @@ def wait_for_agent_interaction(
1122
1265
  ) -> AgentInteractionResult:
1123
1266
  """
1124
1267
  Wait for an agent interaction request or terminal state.
1125
-
1268
+
1126
1269
  This convenience function provides the same functionality as the AsteroidClient.wait_for_agent_interaction method.
1127
-
1270
+
1128
1271
  Args:
1129
1272
  client: The AsteroidClient instance
1130
1273
  execution_id: The execution identifier for an already started execution
1131
1274
  poll_interval: How often to check for updates in seconds (default: 2.0)
1132
1275
  timeout: Maximum wait time in seconds (default: 3600 - 1 hour)
1133
-
1276
+
1134
1277
  Returns:
1135
1278
  AgentInteractionResult containing:
1136
1279
  - is_terminal: True if execution finished (completed/failed/cancelled)
1137
1280
  - status: Current execution status string
1138
1281
  - agent_message: Agent's message if requesting interaction (None if terminal)
1139
1282
  - execution_result: Final result if terminal state (None if requesting interaction)
1140
-
1283
+
1141
1284
  Raises:
1142
1285
  ValueError: If interval or timeout parameters are invalid
1143
1286
  TimeoutError: If the execution times out
1144
1287
  AsteroidAPIError: If API calls fail
1145
-
1288
+
1146
1289
  Example:
1147
1290
  # Start an execution first
1148
1291
  execution_id = execute_agent(client, 'agent-id', {'input': 'test'})
1149
-
1292
+
1150
1293
  # Wait for interaction or completion
1151
1294
  result = wait_for_agent_interaction(client, execution_id)
1152
-
1295
+
1153
1296
  if result.is_terminal:
1154
1297
  print(f"Execution finished with status: {result.status}")
1155
1298
  if result.execution_result:
@@ -1184,6 +1327,8 @@ __all__ = [
1184
1327
  'delete_agent_profile',
1185
1328
  'get_last_n_execution_activities',
1186
1329
  'add_message_to_execution',
1330
+ 'get_execution_files',
1331
+ 'download_execution_file',
1187
1332
  'interactive_agent',
1188
1333
  'wait_for_agent_interaction',
1189
1334
  'get_credentials_public_key',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asteroid-odyssey
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: A Python SDK for browser automation using Asteroid platform.
5
5
  Author-email: David Mlcoch <founders@asteroid.com>
6
6
  License-Expression: MIT
@@ -22,6 +22,7 @@ Requires-Dist: pydantic>=2
22
22
  Requires-Dist: typing-extensions>=4.7.1
23
23
  Requires-Dist: requests>=2.28.0
24
24
  Requires-Dist: cryptography>=41.0.0
25
+ Requires-Dist: lazy-imports>=1.0.0
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: pytest>=7.2.1; extra == "dev"
27
28
  Requires-Dist: pytest-cov>=2.8.1; extra == "dev"
@@ -1,5 +1,5 @@
1
- asteroid_odyssey/__init__.py,sha256=r3WWW_BsZUiHfBcstmWnJxeeDrmWdy-7uovPEHHfO80,915
2
- asteroid_odyssey/client.py,sha256=lcjZ9YMU2w3q05qw2aOVJr78CCaI_FHlkwlAJixpZrQ,45335
1
+ asteroid_odyssey/__init__.py,sha256=Yf0kbvXjjKVBPQV51JCQmId5-CrOhFriHTMOkZmzG2Q,1241
2
+ asteroid_odyssey/client.py,sha256=tER2iiVognPil8SLVLlLs_zayvnP2ojNGSFJ3iGecxU,51216
3
3
  asteroid_odyssey/agents_v1_gen/__init__.py,sha256=bc-NDFcUy9zBjdMcfRgZtKtMotoyz74VMR9jKeCjgkM,4386
4
4
  asteroid_odyssey/agents_v1_gen/api_client.py,sha256=Fq7Uh5yc9Mwza9NgCE1A0g2zrJ_hl5qQ_WFkACeHxWw,27747
5
5
  asteroid_odyssey/agents_v1_gen/api_response.py,sha256=eMxw1mpmJcoGZ3gs9z6jM4oYoZ10Gjk333s9sKxGv7s,652
@@ -31,16 +31,16 @@ asteroid_odyssey/agents_v1_gen/models/status.py,sha256=tnt_4jdoMzcjo6J0vttf2QKLN
31
31
  asteroid_odyssey/agents_v1_gen/models/structured_agent_execution_request.py,sha256=VZyW85pVz9T27aiG4ZlGywnnxTOaJCAjHSDz8D2YxY8,2913
32
32
  asteroid_odyssey/agents_v1_gen/models/update_agent_profile_request.py,sha256=ZggUpiv1G87Ds85n7XDCR0Lc3buwK-1C2kt0Zp3xUM0,5646
33
33
  asteroid_odyssey/agents_v1_gen/models/upload_execution_files200_response.py,sha256=u85oEP2bEuhszonE78VcrB_keT0UZpv16CTGvfse_v4,2735
34
- asteroid_odyssey/agents_v2_gen/__init__.py,sha256=qQ6DKYTAwQ0g4DLl8mQ-CPCh-jZV1lMwZh1hWmcP8Qw,7149
35
- asteroid_odyssey/agents_v2_gen/api_client.py,sha256=KxpVoMofYD5Uwdg7-TXy6TdWgGdtQtaD3UunO3T75MY,27807
34
+ asteroid_odyssey/agents_v2_gen/__init__.py,sha256=WJl7O-wYLcpXDW7detMuJTIGkIGXTQrXk6lPSWgwGKk,13226
35
+ asteroid_odyssey/agents_v2_gen/api_client.py,sha256=ZUwDJpWMg0yMZDVLNzKm3aOqErbYSiH5S01Peuy9A58,27889
36
36
  asteroid_odyssey/agents_v2_gen/api_response.py,sha256=eMxw1mpmJcoGZ3gs9z6jM4oYoZ10Gjk333s9sKxGv7s,652
37
37
  asteroid_odyssey/agents_v2_gen/configuration.py,sha256=WbWZkkdcOX_oqWTdQuBQo07UqU5qlCPUrjMZrfnYtgw,19315
38
38
  asteroid_odyssey/agents_v2_gen/exceptions.py,sha256=hBo-qUqJrW250S_xxUORV_LZU3YWE1-EhXXTxX5-P3k,6474
39
39
  asteroid_odyssey/agents_v2_gen/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  asteroid_odyssey/agents_v2_gen/rest.py,sha256=QUgg1ahRFOoPjd6F0BU4uZ0MxdrxqGQartE6QMMziTI,9487
41
- asteroid_odyssey/agents_v2_gen/api/__init__.py,sha256=HJP59V3S9sayesugWn215uvjoIfhzWxNGRknmMvtRJE,122
42
- asteroid_odyssey/agents_v2_gen/api/execution_api.py,sha256=1rCYuGB2S4dgsFJmocRXXj9IVig7dEdx1xyGXwRF5bA,25365
43
- asteroid_odyssey/agents_v2_gen/models/__init__.py,sha256=k238x-mE3t97EpcO5BfLjlX9NxyFgG20C2TS68w2ots,3821
41
+ asteroid_odyssey/agents_v2_gen/api/__init__.py,sha256=oeEY7eu1t3zOeECrSECzmdkDzsykJ4oWl-VJcd7DEPk,497
42
+ asteroid_odyssey/agents_v2_gen/api/execution_api.py,sha256=ym_nDCqVZwVRJBQLVySYWguYdx7TV17okTeKUchqeoY,35231
43
+ asteroid_odyssey/agents_v2_gen/models/__init__.py,sha256=DmHBb09PPbr9xHgqP335KmO6yP3ZBPQS8WV5yjbOwnw,7817
44
44
  asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_completed.py,sha256=djIqJOhSsyNDehLRJ_RKfjhZyQL596TNz4XhYC65FgY,3389
45
45
  asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_failed.py,sha256=Ld0T3X-TjRwd3PnBObiHE7jFpHBejRXgt8ENB486zU8,3359
46
46
  asteroid_odyssey/agents_v2_gen/models/activity_payload_union_action_started.py,sha256=ZVanUlpD1p5u1woVZ1YWi6qpj2X1Yg1twTCVI-ZoBB0,3369
@@ -52,7 +52,7 @@ asteroid_odyssey/agents_v2_gen/models/activity_payload_union_terminal.py,sha256=
52
52
  asteroid_odyssey/agents_v2_gen/models/activity_payload_union_transitioned_node.py,sha256=pOou5D9n0ojtImrU_0KRW8EylA1eTwlFvvF0n7HI3-M,3399
53
53
  asteroid_odyssey/agents_v2_gen/models/activity_payload_union_user_message_received.py,sha256=DtvCeoXNOyBEDmwXYZmSkPtbEsxhFX_ngtOvgrPLwWk,3432
54
54
  asteroid_odyssey/agents_v2_gen/models/error.py,sha256=gxvmPlORWeuW8WBKxTQhWX9fT6ILNUkz5OyNue0JiJE,2527
55
- asteroid_odyssey/agents_v2_gen/models/execution_activity.py,sha256=hs47z2cEQ3w4EUmg3dc2HUYp3uHbWu_27UegbT_wEj0,3179
55
+ asteroid_odyssey/agents_v2_gen/models/execution_activity.py,sha256=hYyLoogCuNWsH-Gh8WZQnjfQIHbe0bi99mTmdo3kWSE,3180
56
56
  asteroid_odyssey/agents_v2_gen/models/execution_activity_action_completed_payload.py,sha256=RMDQhAjJh-q_xoYbFazJD_VELpw67Vc7Gbs3eQeDSGc,2587
57
57
  asteroid_odyssey/agents_v2_gen/models/execution_activity_action_failed_payload.py,sha256=hRaq3_seIFpTB16d8CbZsLiAO9_Xmav_fdFUTBLwucY,2575
58
58
  asteroid_odyssey/agents_v2_gen/models/execution_activity_action_started_payload.py,sha256=yC9InGz_D_CyIFiC5ZOUBjh0VpTpfb8k_tfIviEOdFA,2579
@@ -61,8 +61,8 @@ asteroid_odyssey/agents_v2_gen/models/execution_activity_payload_union.py,sha256
61
61
  asteroid_odyssey/agents_v2_gen/models/execution_activity_status_changed_payload.py,sha256=jdBn1YAHmwTuveFAIoELV--NdpeUw0NyJorlwz1cgCc,5073
62
62
  asteroid_odyssey/agents_v2_gen/models/execution_activity_step_completed_payload.py,sha256=SOC8Y7Ox5POHbrACjYUX9wUkEnGFgWdRV60dhqhfbPg,2627
63
63
  asteroid_odyssey/agents_v2_gen/models/execution_activity_step_started_payload.py,sha256=6IIbpvDKLIDzc5sr2KaHcGGKPDnUu4iPR-LlXChysT0,2619
64
- asteroid_odyssey/agents_v2_gen/models/execution_activity_transitioned_node_payload.py,sha256=nvHGIaaF37ol12EjU0RduaUnRhUaDpKD1qKStpJ7qLE,2769
65
- asteroid_odyssey/agents_v2_gen/models/execution_activity_user_message_received_payload.py,sha256=47US747NjJy11BsuwggH-o8-UhO3HUDamnNA4KISjjM,2718
64
+ asteroid_odyssey/agents_v2_gen/models/execution_activity_transitioned_node_payload.py,sha256=si1W3r5G1ij56WAeu2nsV3O8l2tOucqpwzYZBoXMLBs,2786
65
+ asteroid_odyssey/agents_v2_gen/models/execution_activity_user_message_received_payload.py,sha256=8ZG0-tHo1Zd9ukafQx1Q4Cl6DY6DNmKNKfzKZymGklU,2735
66
66
  asteroid_odyssey/agents_v2_gen/models/execution_awaiting_confirmation_payload.py,sha256=KFgB1DITaMhPgITwFCdvuhCEn1WnJ9IQxMLCLvIc7X4,2571
67
67
  asteroid_odyssey/agents_v2_gen/models/execution_completed_payload.py,sha256=iFozfv2PoqHJ83lTqbIqo0RKPKww-knEeqnY434A8AA,3306
68
68
  asteroid_odyssey/agents_v2_gen/models/execution_failed_payload.py,sha256=dzIk6XgTy1BWBp963FTnDQasrrkRb5fsshOkIsn_j54,2515
@@ -70,8 +70,9 @@ asteroid_odyssey/agents_v2_gen/models/execution_paused_payload.py,sha256=scUIg3_
70
70
  asteroid_odyssey/agents_v2_gen/models/execution_status.py,sha256=SHpsS6IzN42kPdDiykmKudEzmCiR1yRttJ9nG206W-g,959
71
71
  asteroid_odyssey/agents_v2_gen/models/execution_terminal_payload.py,sha256=hWh7Fuy0fswZZFwXncRwdtlyDD4WGEA-wYYdtOg8-vQ,2935
72
72
  asteroid_odyssey/agents_v2_gen/models/execution_user_messages_add_text_body.py,sha256=BQ9klnV_2-3svbyPFcGC272y7uKanIKtpbwVPXQs6gk,2559
73
+ asteroid_odyssey/agents_v2_gen/models/file.py,sha256=iDgbR_tQoLxBXqFmOg3SsShQGdZwU0xpw7O9pte4xvo,3576
73
74
  asteroid_odyssey/agents_v2_gen/models/versions.py,sha256=54MndkW0A32LnZbTGHEsFnNRrrT112iWNJMgk7-a2eM,736
74
- asteroid_odyssey-1.3.1.dist-info/METADATA,sha256=fjCqNaAB4EAyUb0dhY3OToLWfbQ2h2_LsHuX9ewMvcI,7073
75
- asteroid_odyssey-1.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
76
- asteroid_odyssey-1.3.1.dist-info/top_level.txt,sha256=h4T6NKscnThJ4Nhzors2NKlJeZzepnM7XvDgsnfi5HA,17
77
- asteroid_odyssey-1.3.1.dist-info/RECORD,,
75
+ asteroid_odyssey-1.3.3.dist-info/METADATA,sha256=i6bZ3IeI1jT3eJcMbilCOE5G6fTRwRnlbeEO7SCWWqM,7108
76
+ asteroid_odyssey-1.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ asteroid_odyssey-1.3.3.dist-info/top_level.txt,sha256=h4T6NKscnThJ4Nhzors2NKlJeZzepnM7XvDgsnfi5HA,17
78
+ asteroid_odyssey-1.3.3.dist-info/RECORD,,