airbyte-agent-mcp 0.1.30__py3-none-any.whl → 0.1.53__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 (22) hide show
  1. airbyte_agent_mcp/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
  2. airbyte_agent_mcp/_vendored/connector_sdk/cloud_utils/client.py +213 -0
  3. airbyte_agent_mcp/_vendored/connector_sdk/connector_model_loader.py +31 -4
  4. airbyte_agent_mcp/_vendored/connector_sdk/constants.py +1 -1
  5. airbyte_agent_mcp/_vendored/connector_sdk/executor/hosted_executor.py +93 -84
  6. airbyte_agent_mcp/_vendored/connector_sdk/executor/local_executor.py +93 -23
  7. airbyte_agent_mcp/_vendored/connector_sdk/extensions.py +42 -3
  8. airbyte_agent_mcp/_vendored/connector_sdk/http_client.py +50 -43
  9. airbyte_agent_mcp/_vendored/connector_sdk/introspection.py +262 -0
  10. airbyte_agent_mcp/_vendored/connector_sdk/observability/config.py +179 -0
  11. airbyte_agent_mcp/_vendored/connector_sdk/observability/session.py +35 -28
  12. airbyte_agent_mcp/_vendored/connector_sdk/schema/base.py +2 -1
  13. airbyte_agent_mcp/_vendored/connector_sdk/schema/components.py +2 -1
  14. airbyte_agent_mcp/_vendored/connector_sdk/schema/operations.py +1 -1
  15. airbyte_agent_mcp/_vendored/connector_sdk/schema/security.py +10 -0
  16. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/events.py +2 -1
  17. airbyte_agent_mcp/_vendored/connector_sdk/telemetry/tracker.py +3 -0
  18. airbyte_agent_mcp/_vendored/connector_sdk/types.py +7 -1
  19. airbyte_agent_mcp/config.py +1 -1
  20. {airbyte_agent_mcp-0.1.30.dist-info → airbyte_agent_mcp-0.1.53.dist-info}/METADATA +1 -1
  21. {airbyte_agent_mcp-0.1.30.dist-info → airbyte_agent_mcp-0.1.53.dist-info}/RECORD +22 -18
  22. {airbyte_agent_mcp-0.1.30.dist-info → airbyte_agent_mcp-0.1.53.dist-info}/WHEEL +0 -0
@@ -0,0 +1,5 @@
1
+ """Cloud API utilities for Airbyte Platform integration."""
2
+
3
+ from .client import AirbyteCloudClient
4
+
5
+ __all__ = ["AirbyteCloudClient"]
@@ -0,0 +1,213 @@
1
+ """AirbyteCloudClient for Airbyte Platform API integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timedelta
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+
11
+ class AirbyteCloudClient:
12
+ """Client for interacting with Airbyte Platform APIs.
13
+
14
+ Handles authentication, token caching, and API calls to:
15
+ - Get bearer tokens for authentication
16
+ - Look up connector instances for users
17
+ - Execute connectors via the cloud API
18
+
19
+ Example:
20
+ client = AirbyteCloudClient(
21
+ client_id="your-client-id",
22
+ client_secret="your-client-secret"
23
+ )
24
+
25
+ # Get a connector instance
26
+ instance_id = await client.get_connector_instance_id(
27
+ external_user_id="user-123",
28
+ connector_definition_id="stripe-def-456"
29
+ )
30
+
31
+ # Execute the connector
32
+ result = await client.execute_connector(
33
+ instance_id=instance_id,
34
+ entity="customers",
35
+ action="list",
36
+ params={"limit": 10}
37
+ )
38
+ """
39
+
40
+ AUTH_BASE_URL = "https://cloud.airbyte.com" # For token endpoint
41
+ API_BASE_URL = "https://api.airbyte.ai" # For instance lookup & execution
42
+
43
+ def __init__(self, client_id: str, client_secret: str):
44
+ """Initialize AirbyteCloudClient.
45
+
46
+ Args:
47
+ client_id: Airbyte client ID for authentication
48
+ client_secret: Airbyte client secret for authentication
49
+ """
50
+ self._client_id = client_id
51
+ self._client_secret = client_secret
52
+
53
+ # Token cache (instance-level)
54
+ self._cached_token: str | None = None
55
+ self._token_expires_at: datetime | None = None
56
+ self._http_client = httpx.AsyncClient(
57
+ timeout=httpx.Timeout(300.0), # 5 minute timeout
58
+ follow_redirects=True,
59
+ )
60
+
61
+ async def get_bearer_token(self) -> str:
62
+ """Get bearer token for API authentication.
63
+
64
+ Caches the token and only requests a new one when the cached token
65
+ is expired or missing. Adds a 60-second buffer before expiration
66
+ to avoid edge cases.
67
+
68
+ Returns:
69
+ Bearer token string
70
+
71
+ Raises:
72
+ httpx.HTTPStatusError: If the token request fails with 4xx/5xx
73
+ httpx.RequestError: If the network request fails
74
+
75
+ Example:
76
+ token = await client.get_bearer_token()
77
+ # Use token in Authorization header: f"Bearer {token}"
78
+ """
79
+ # Check if we have a cached token that hasn't expired
80
+ if self._cached_token and self._token_expires_at:
81
+ # Add 60 second buffer before expiration to avoid edge cases
82
+ now = datetime.now()
83
+ if now < self._token_expires_at:
84
+ # Token is still valid, return cached version
85
+ return self._cached_token
86
+
87
+ # Token is missing or expired, fetch a new one
88
+ url = f"{self.AUTH_BASE_URL}/api/v1/applications/token"
89
+ request_body = {
90
+ "client_id": self._client_id,
91
+ "client_secret": self._client_secret,
92
+ }
93
+
94
+ response = await self._http_client.post(url, json=request_body)
95
+ response.raise_for_status()
96
+
97
+ data = response.json()
98
+ access_token = data["access_token"]
99
+ expires_in = 15 * 60 # default 15 min expiry time * 60 seconds
100
+
101
+ # Calculate expiration time with 60 second buffer
102
+ expires_at = datetime.now() + timedelta(seconds=expires_in - 60)
103
+ self._cached_token = access_token
104
+ self._token_expires_at = expires_at
105
+
106
+ return access_token
107
+
108
+ async def get_connector_instance_id(
109
+ self,
110
+ external_user_id: str,
111
+ connector_definition_id: str,
112
+ ) -> str:
113
+ """Get connector instance ID for a user.
114
+
115
+ Looks up the connector instance that belongs to the specified user
116
+ and connector definition. Validates that exactly one instance exists.
117
+
118
+ Args:
119
+ external_user_id: User identifier in the Airbyte system
120
+ connector_definition_id: UUID of the connector definition
121
+
122
+ Returns:
123
+ Connector instance ID (UUID string)
124
+
125
+ Raises:
126
+ ValueError: If 0 or more than 1 instance is found
127
+ httpx.HTTPStatusError: If API returns 4xx/5xx status code
128
+ httpx.RequestError: If network request fails
129
+
130
+ Example:
131
+ instance_id = await client.get_connector_instance_id(
132
+ external_user_id="user-123",
133
+ connector_definition_id="550e8400-e29b-41d4-a716-446655440000"
134
+ )
135
+ """
136
+
137
+ token = await self.get_bearer_token()
138
+ url = f"{self.API_BASE_URL}/api/v1/connectors/instances_for_user"
139
+ params = {
140
+ "external_user_id": external_user_id,
141
+ "definition_id": connector_definition_id,
142
+ }
143
+
144
+ headers = {"Authorization": f"Bearer {token}"}
145
+ response = await self._http_client.get(url, params=params, headers=headers)
146
+ response.raise_for_status()
147
+
148
+ data = response.json()
149
+ instances = data["instances"]
150
+
151
+ if len(instances) == 0:
152
+ raise ValueError(f"No connector instance found for user '{external_user_id}' " f"and connector '{connector_definition_id}'")
153
+
154
+ if len(instances) > 1:
155
+ raise ValueError(
156
+ f"Multiple connector instances found for user '{external_user_id}' "
157
+ f"and connector '{connector_definition_id}'. Expected exactly 1, "
158
+ f"found {len(instances)}"
159
+ )
160
+
161
+ instance_id = instances[0]["id"]
162
+ return instance_id
163
+
164
+ async def execute_connector(
165
+ self,
166
+ instance_id: str,
167
+ entity: str,
168
+ action: str,
169
+ params: dict[str, Any] | None,
170
+ ) -> dict[str, Any]:
171
+ """Execute a connector operation.
172
+
173
+ Args:
174
+ instance_id: Connector instance UUID
175
+ entity: Entity name (e.g., "customers", "invoices")
176
+ action: Operation action (e.g., "list", "get", "create")
177
+ params: Optional parameters for the operation
178
+
179
+ Returns:
180
+ Raw JSON response dict from the API
181
+
182
+ Raises:
183
+ httpx.HTTPStatusError: If API returns 4xx/5xx status code
184
+ httpx.RequestError: If network request fails
185
+
186
+ Example:
187
+ result = await client.execute_connector(
188
+ instance_id="inst-123",
189
+ entity="customers",
190
+ action="list",
191
+ params={"limit": 10}
192
+ )
193
+ """
194
+ token = await self.get_bearer_token()
195
+ url = f"{self.API_BASE_URL}/api/v1/connectors/instances/{instance_id}/execute"
196
+ headers = {"Authorization": f"Bearer {token}"}
197
+ request_body = {
198
+ "entity": entity,
199
+ "action": action,
200
+ "params": params,
201
+ }
202
+
203
+ response = await self._http_client.post(url, json=request_body, headers=headers)
204
+ response.raise_for_status()
205
+
206
+ return response.json()
207
+
208
+ async def close(self):
209
+ """Close the HTTP client.
210
+
211
+ Call this when you're done using the client to clean up resources.
212
+ """
213
+ await self._http_client.aclose()
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import re
6
6
  from pathlib import Path
7
7
  from typing import Any
8
+ from uuid import UUID
8
9
 
9
10
  import jsonref
10
11
  import yaml
@@ -105,7 +106,7 @@ def resolve_schema_refs(schema: Any, spec_dict: dict) -> dict[str, Any]:
105
106
 
106
107
  try:
107
108
  # Resolve all references
108
- resolved_spec = jsonref.replace_refs(
109
+ resolved_spec = jsonref.replace_refs( # type: ignore[union-attr]
109
110
  temp_spec,
110
111
  base_uri="",
111
112
  jsonschema=True, # Use JSONSchema draft 7 semantics
@@ -117,9 +118,11 @@ def resolve_schema_refs(schema: Any, spec_dict: dict) -> dict[str, Any]:
117
118
 
118
119
  # Remove any remaining jsonref proxy objects by converting to plain dict
119
120
  return _deproxy_schema(resolved_schema)
120
- except (jsonref.JsonRefError, KeyError, RecursionError):
121
+ except (AttributeError, KeyError, RecursionError, Exception):
121
122
  # If resolution fails, return the original schema
122
123
  # This allows the system to continue even with malformed $refs
124
+ # AttributeError covers the case where jsonref might be None
125
+ # Exception catches jsonref.JsonRefError and other jsonref exceptions
123
126
  return schema_dict
124
127
 
125
128
 
@@ -390,23 +393,35 @@ def convert_openapi_to_connector_model(spec: OpenAPIConnector) -> ConnectorModel
390
393
  for entity_name, endpoints_dict in entities_map.items():
391
394
  actions = list(endpoints_dict.keys())
392
395
 
393
- # Get schema from components if available
396
+ # Get schema and stream_name from components if available
394
397
  schema = None
398
+ entity_stream_name = None
395
399
  if spec.components:
396
400
  # Look for a schema matching the entity name
397
401
  for schema_name, schema_def in spec.components.schemas.items():
398
402
  if schema_def.x_airbyte_entity_name == entity_name or schema_name.lower() == entity_name.lower():
399
403
  schema = schema_def.model_dump(by_alias=True)
404
+ entity_stream_name = schema_def.x_airbyte_stream_name
400
405
  break
401
406
 
402
- entity = EntityDefinition(name=entity_name, actions=actions, endpoints=endpoints_dict, schema=schema)
407
+ entity = EntityDefinition(
408
+ name=entity_name,
409
+ stream_name=entity_stream_name,
410
+ actions=actions,
411
+ endpoints=endpoints_dict,
412
+ schema=schema,
413
+ )
403
414
  entities.append(entity)
404
415
 
405
416
  # Extract retry config from x-airbyte-retry-config extension
406
417
  retry_config = spec.info.x_airbyte_retry_config
418
+ connector_id = spec.info.x_airbyte_connector_id
419
+ if not connector_id:
420
+ raise InvalidOpenAPIError("Missing required x-airbyte-connector-id field")
407
421
 
408
422
  # Create ConnectorModel
409
423
  model = ConnectorModel(
424
+ id=connector_id,
410
425
  name=name,
411
426
  version=version,
412
427
  base_url=base_url,
@@ -926,8 +941,20 @@ def load_connector_model(definition_path: str | Path) -> ConnectorModel:
926
941
  )
927
942
  entities.append(entity)
928
943
 
944
+ # Get connector ID
945
+ connector_id_value = connector_meta.get("id")
946
+ if connector_id_value:
947
+ # Try to parse as UUID (handles string UUIDs)
948
+ if isinstance(connector_id_value, str):
949
+ connector_id = UUID(connector_id_value)
950
+ else:
951
+ connector_id = connector_id_value
952
+ else:
953
+ raise ValueError
954
+
929
955
  # Build ConnectorModel
930
956
  model = ConnectorModel(
957
+ id=connector_id,
931
958
  name=connector_meta["name"],
932
959
  version=connector_meta.get("version", OPENAPI_DEFAULT_VERSION),
933
960
  base_url=raw_definition.get("base_url", connector_meta.get("base_url", "")),
@@ -74,5 +74,5 @@ except PackageNotFoundError:
74
74
  SDK_VERSION = "0.0.0-dev"
75
75
  """Current version of the Airbyte SDK."""
76
76
 
77
- MINIMUM_PYTHON_VERSION = "3.9"
77
+ MINIMUM_PYTHON_VERSION = "3.13"
78
78
  """Minimum Python version required to run the SDK."""
@@ -1,12 +1,11 @@
1
- """Hosted executor for proxying operations through the backend API."""
1
+ """Hosted executor for proxying operations through the cloud API."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
-
7
- import httpx
8
5
  from opentelemetry import trace
9
6
 
7
+ from ..cloud_utils import AirbyteCloudClient
8
+
10
9
  from .models import (
11
10
  ExecutionConfig,
12
11
  ExecutionResult,
@@ -14,30 +13,36 @@ from .models import (
14
13
 
15
14
 
16
15
  class HostedExecutor:
17
- """Executor that proxies execution through the Sonar backend API.
16
+ """Executor that proxies execution through the Airbyte Cloud API.
18
17
 
19
- This is the "hosted mode" executor that makes HTTP calls to the backend API
20
- instead of directly calling external services. The backend handles all
18
+ This is the "hosted mode" executor that makes HTTP calls to the cloud API
19
+ instead of directly calling external services. The cloud API handles all
21
20
  connector logic, secrets management, and execution.
22
21
 
23
- The API URL is configured at initialization via the api_url parameter,
24
- which defaults to the AIRBYTE_CONNECTOR_API_URL environment variable.
22
+ The executor takes an external_user_id and uses the AirbyteCloudClient to:
23
+ 1. Authenticate with the Airbyte Platform (bearer token with caching)
24
+ 2. Look up the user's connector instance
25
+ 3. Execute the connector operation via the cloud API
25
26
 
26
27
  Implements ExecutorProtocol.
27
28
 
28
29
  Example:
30
+ # Create executor with user ID, credentials, and connector definition ID
29
31
  executor = HostedExecutor(
30
- connector_id="stripe-prod-123",
32
+ external_user_id="user-123",
31
33
  airbyte_client_id="client_abc123",
32
- airbyte_client_secret="secret_xyz789"
34
+ airbyte_client_secret="secret_xyz789",
35
+ connector_definition_id="abc123-def456-ghi789",
33
36
  )
34
37
 
35
- config = ExecutionConfig(
38
+ # Execute an operation
39
+ execution_config = ExecutionConfig(
36
40
  entity="customers",
37
- action="list"
41
+ action="list",
42
+ params={"limit": 10}
38
43
  )
39
44
 
40
- result = await executor.execute(config)
45
+ result = await executor.execute(execution_config)
41
46
  if result.success:
42
47
  print(f"Data: {result.data}")
43
48
  else:
@@ -46,53 +51,45 @@ class HostedExecutor:
46
51
 
47
52
  def __init__(
48
53
  self,
49
- connector_id: str,
54
+ external_user_id: str,
50
55
  airbyte_client_id: str,
51
56
  airbyte_client_secret: str,
52
- api_url: str | None = None,
57
+ connector_definition_id: str,
53
58
  ):
54
59
  """Initialize hosted executor.
55
60
 
56
61
  Args:
57
- connector_id: ID of the connector to execute (e.g., "stripe-prod-123")
62
+ external_user_id: User identifier in the Airbyte system
58
63
  airbyte_client_id: Airbyte client ID for authentication
59
64
  airbyte_client_secret: Airbyte client secret for authentication
60
- api_url: API URL for the hosted executor backend. Defaults to
61
- AIRBYTE_CONNECTOR_API_URL environment variable or "http://localhost:8001"
65
+ connector_definition_id: Connector definition ID used to look up
66
+ the user's connector instance.
62
67
 
63
68
  Example:
64
69
  executor = HostedExecutor(
65
- connector_id="my-connector-id",
66
- airbyte_client_id="client_abc123",
67
- airbyte_client_secret="secret_xyz789"
68
- )
69
-
70
- # Or with custom API URL:
71
- executor = HostedExecutor(
72
- connector_id="my-connector-id",
70
+ external_user_id="user-123",
73
71
  airbyte_client_id="client_abc123",
74
72
  airbyte_client_secret="secret_xyz789",
75
- api_url="https://api.production.com"
73
+ connector_definition_id="abc123-def456-ghi789",
76
74
  )
77
75
  """
78
- self.connector_id = connector_id
79
- self.airbyte_client_id = airbyte_client_id
80
- self.airbyte_client_secret = airbyte_client_secret
81
- self.api_url = api_url or os.getenv("AIRBYTE_CONNECTOR_API_URL", "http://localhost:8001")
82
-
83
- # Create synchronous HTTP client
84
- # We use sync client even though execute() is async for simplicity
85
- # The async wrapper allows it to work with the protocol
86
- self.client = httpx.Client(
87
- timeout=httpx.Timeout(300.0), # 5 minute timeout
88
- follow_redirects=True,
76
+ self._external_user_id = external_user_id
77
+ self._connector_definition_id = connector_definition_id
78
+
79
+ # Create AirbyteCloudClient for API interactions
80
+ self._cloud_client = AirbyteCloudClient(
81
+ client_id=airbyte_client_id,
82
+ client_secret=airbyte_client_secret,
89
83
  )
90
84
 
91
85
  async def execute(self, config: ExecutionConfig) -> ExecutionResult:
92
- """Execute connector via backend API (ExecutorProtocol implementation).
86
+ """Execute connector via cloud API (ExecutorProtocol implementation).
93
87
 
94
- Makes an HTTP POST request to /connectors/{connector_id}/execute with
95
- OAuth authentication and the configuration in the request body.
88
+ Flow:
89
+ 1. Get connector id from connector model
90
+ 2. Look up the user's connector instance ID
91
+ 3. Execute the connector operation via the cloud API
92
+ 4. Parse the response into ExecutionResult
96
93
 
97
94
  Args:
98
95
  config: Execution configuration (entity, action, params)
@@ -101,88 +98,100 @@ class HostedExecutor:
101
98
  ExecutionResult with success/failure status
102
99
 
103
100
  Raises:
101
+ ValueError: If no instance or multiple instances found for user
104
102
  httpx.HTTPStatusError: If API returns 4xx/5xx status code
105
103
  httpx.RequestError: If network request fails
106
104
 
107
105
  Example:
108
106
  config = ExecutionConfig(
109
107
  entity="customers",
110
- action="list"
108
+ action="list",
109
+ params={"limit": 10}
111
110
  )
112
111
  result = await executor.execute(config)
113
112
  """
114
113
  tracer = trace.get_tracer("airbyte.connector-sdk.executor.hosted")
115
114
 
116
115
  with tracer.start_as_current_span("airbyte.hosted_executor.execute") as span:
117
- # Add span attributes
118
- span.set_attribute("connector.id", self.connector_id)
116
+ # Add span attributes for observability
117
+ span.set_attribute("connector.definition_id", self._connector_definition_id)
119
118
  span.set_attribute("connector.entity", config.entity)
120
119
  span.set_attribute("connector.action", config.action)
121
- span.set_attribute("connector.api_url", self.api_url)
120
+ span.set_attribute("user.external_id", self._external_user_id)
122
121
  if config.params:
123
122
  # Only add non-sensitive param keys
124
123
  span.set_attribute("connector.param_keys", list(config.params.keys()))
125
124
 
126
- # Build API URL from instance api_url
127
- url = f"{self.api_url}/connectors/{self.connector_id}/execute"
128
- span.set_attribute("http.url", url)
129
-
130
- # Build request body matching ExecutionRequest model
131
- # Extract entity, action, and params from config attributes
132
- request_body = {
133
- "entity": config.entity,
134
- "action": config.action,
135
- "params": config.params,
136
- }
137
-
138
125
  try:
139
- # Make synchronous HTTP request
140
- # (wrapped in async method for protocol compatibility)
141
- response = self.client.post(url, json=request_body)
126
+ # Step 1: Get connector definition id
127
+ connector_definition_id = self._connector_definition_id
128
+
129
+ # Step 2: Get the connector instance ID for this user
130
+ instance_id = await self._cloud_client.get_connector_instance_id(
131
+ external_user_id=self._external_user_id,
132
+ connector_definition_id=connector_definition_id,
133
+ )
142
134
 
143
- # Add response status code to span
144
- span.set_attribute("http.status_code", response.status_code)
135
+ span.set_attribute("connector.instance_id", instance_id)
145
136
 
146
- # Raise exception for 4xx/5xx status codes
147
- response.raise_for_status()
137
+ # Step 3: Execute the connector via the cloud API
138
+ response = await self._cloud_client.execute_connector(
139
+ instance_id=instance_id,
140
+ entity=config.entity,
141
+ action=config.action,
142
+ params=config.params,
143
+ )
148
144
 
149
- # Parse JSON response
150
- result_data = response.json()
145
+ # Step 4: Parse the response into ExecutionResult
146
+ # The response_data is a dict from the API
147
+ result = self._parse_execution_result(response)
151
148
 
152
149
  # Mark span as successful
153
- span.set_attribute("connector.success", True)
150
+ span.set_attribute("connector.success", result.success)
154
151
 
155
- # Return success result
156
- return ExecutionResult(success=True, data=result_data, error=None)
152
+ return result
157
153
 
158
- except httpx.HTTPStatusError as e:
159
- # HTTP error (4xx, 5xx) - record and re-raise
154
+ except ValueError as e:
155
+ # Instance lookup validation error (0 or >1 instances)
160
156
  span.set_attribute("connector.success", False)
161
- span.set_attribute("connector.error_type", "HTTPStatusError")
162
- span.set_attribute("http.status_code", e.response.status_code)
157
+ span.set_attribute("connector.error_type", "ValueError")
163
158
  span.record_exception(e)
164
159
  raise
165
160
 
166
161
  except Exception as e:
167
- # Catch-all for any other unexpected exceptions
162
+ # HTTP errors and other exceptions
168
163
  span.set_attribute("connector.success", False)
169
164
  span.set_attribute("connector.error_type", type(e).__name__)
170
165
  span.record_exception(e)
171
166
  raise
172
167
 
173
- def close(self):
174
- """Close the HTTP client.
168
+ def _parse_execution_result(self, response: dict) -> ExecutionResult:
169
+ """Parse API response into ExecutionResult.
175
170
 
176
- Call this when you're done using the executor to clean up resources.
171
+ Args:
172
+ response_data: Raw JSON response from the cloud API
173
+
174
+ Returns:
175
+ ExecutionResult with parsed data
176
+ """
177
+
178
+ return ExecutionResult(
179
+ success=True,
180
+ data=response["result"],
181
+ meta=response.get("connector_metadata"),
182
+ error=None,
183
+ )
184
+
185
+ async def close(self):
186
+ """Close the cloud client and cleanup resources.
187
+
188
+ Call this when you're done using the executor to clean up HTTP connections.
177
189
 
178
190
  Example:
179
- executor = HostedExecutor(
180
- workspace_id="workspace-123",
181
- connector_id="my-connector"
182
- )
191
+ executor = HostedExecutor(...)
183
192
  try:
184
193
  result = await executor.execute(config)
185
194
  finally:
186
- executor.close()
195
+ await executor.close()
187
196
  """
188
- self.client.close()
197
+ await self._cloud_client.close()