airbyte.agents.connectors

Airbyte Agents connectors, and the single-action execute interface.

⚠️ Experimental Interface

The Airbyte Agents Python interfaces are experimental. Class names, method signatures, and result models may change or be removed without notice between minor versions of PyAirbyte. Pin an exact PyAirbyte version if you depend on them.

  1# Copyright (c) 2026 Airbyte, Inc., all rights reserved.
  2"""Airbyte Agents connectors, and the single-action `execute` interface.
  3
  4> ## ⚠️ Experimental Interface
  5>
  6> **The Airbyte Agents Python interfaces are experimental.** Class names, method signatures,
  7> and result models may change or be removed without notice between minor versions of
  8> PyAirbyte. Pin an exact PyAirbyte version if you depend on them.
  9"""
 10
 11from __future__ import annotations
 12
 13from enum import Enum
 14from typing import TYPE_CHECKING, Any, NamedTuple
 15
 16from airbyte.agents import _api_util
 17from airbyte.agents.models import AgentConnectorDetails, AgentExecuteResult
 18from airbyte.exceptions import PyAirbyteInputError
 19
 20
 21if TYPE_CHECKING:
 22    from collections.abc import Iterator
 23
 24    from airbyte.cloud._credentials import _AirbyteCredentials
 25
 26
 27UNSUPPORTED_ACTIONS: set[str] = {"download"}
 28"""Actions PyAirbyte rejects before sending them to the Agents API.
 29
 30`download` returns a binary stream rather than JSON, and PyAirbyte does not yet support
 31streaming responses, so it is rejected with actionable guidance instead of failing later
 32inside the transport layer.
 33"""
 34
 35
 36class AgentReadAction(str, Enum):
 37    """Connector actions that only read data.
 38
 39    The `search` action is the connector's native API search, parallel to `get` and `list`.
 40    The `sql_select` action runs one read-only SQL statement (or `SHOW TABLES`) on the query
 41    engine behind a destination connector. Pass `sql` and `sql_dialect` (and optionally
 42    `dry_run`) in `api_args`; `entity_type` is ignored for this action.
 43
 44    The `download` action is deliberately absent even though it reads: it returns a binary
 45    stream rather than JSON, which PyAirbyte does not yet support.
 46    """
 47
 48    LIST = "list"
 49    GET = "get"
 50    SEARCH = "search"
 51    SQL_SELECT = "sql_select"
 52
 53
 54class AgentWriteAction(str, Enum):
 55    """Connector actions that create, update, or delete data."""
 56
 57    CREATE = "create"
 58    UPDATE = "update"
 59    DELETE = "delete"
 60
 61
 62AgentAction = AgentReadAction | AgentWriteAction
 63"""Every connector action accepted by `AgentConnector.execute()`."""
 64
 65
 66_PAGINATION_ARGS: dict[str, str] = {"page_size": "limit", "cursor": "cursor"}
 67"""Pagination conveniences PyAirbyte merges into the connector's `params`.
 68
 69Maps the PyAirbyte argument name to the connector's own `params` key: the Agents API calls
 70page size `limit`, which PyAirbyte does not expose under that name because `limit` reads as
 71a cap on the whole result set rather than on one page.
 72"""
 73
 74
 75class _ConnectorLookup(NamedTuple):
 76    """What to look a connector up by, once the lookup arguments have been validated.
 77
 78    Both fields are set when the caller passed a positional value that could be either an
 79    ID or a name, in which case an ID match takes precedence over a name match.
 80    """
 81
 82    connector_id: str | None
 83    name: str | None
 84
 85
 86def _resolve_connector_lookup(
 87    id_or_name: str | None,
 88    /,
 89    *,
 90    id: str | None,  # noqa: A002  # Mirrors the public `id` alias it validates.
 91    connector_id: str | None,
 92    name: str | None,
 93) -> _ConnectorLookup:
 94    """Validate connector lookup arguments and return what to look the connector up by.
 95
 96    `id` and `connector_id` are synonyms, so exactly one of them, `name`, or the positional
 97    `id_or_name` is required. Conflicting synonym values are rejected, as is a blank value,
 98    which would otherwise be treated as an omitted argument.
 99    """
100    all_args = {
101        "id_or_name": id_or_name,
102        "id": id,
103        "connector_id": connector_id,
104        "name": name,
105    }
106
107    blank_args = sorted(
108        key for key, value in all_args.items() if value is not None and not value.strip()
109    )
110    if blank_args:
111        raise PyAirbyteInputError(
112            message="Connector lookup arguments cannot be blank.",
113            guidance="Omit the argument entirely, or pass a non-blank value.",
114            context={"blank_args": blank_args},
115        )
116
117    if id_or_name:
118        keyword_args = sorted(
119            key for key, value in all_args.items() if value and key != "id_or_name"
120        )
121        if keyword_args:
122            raise PyAirbyteInputError(
123                message="A positional connector lookup cannot be combined with keyword arguments.",
124                guidance="Pass the value positionally, or pass `id`, `connector_id`, or `name`.",
125                context={"keyword_args": keyword_args},
126            )
127        return _ConnectorLookup(connector_id=id_or_name, name=id_or_name)
128
129    provided = {
130        key: value for key, value in {"id": id, "connector_id": connector_id}.items() if value
131    }
132    if len(set(provided.values())) > 1:
133        raise PyAirbyteInputError(
134            message="`id` and `connector_id` were given conflicting values.",
135            guidance="These arguments are synonyms, so pass only one of them.",
136            context={"provided": sorted(provided)},
137        )
138
139    if bool(provided) == bool(name):
140        raise PyAirbyteInputError(
141            message="Exactly one connector lookup argument is required.",
142            guidance=(
143                "Pass a connector ID or name positionally, or as `id`, `connector_id`, "
144                "or `name`."
145            ),
146        )
147
148    return _ConnectorLookup(connector_id=next(iter(provided.values()), None), name=name)
149
150
151class AgentConnector:
152    """A connector in an Airbyte Agents workspace.
153
154    Get one from `AgentWorkspace.get_connector()` rather than constructing it directly.
155
156    ```python
157    from airbyte import agents
158
159    workspace = agents.AgentWorkspace.from_env()
160    connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
161    result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
162    for entity in result.entities:
163        print(entity["title"])
164    ```
165    """
166
167    def __init__(
168        self,
169        connector_id: str,
170        *,
171        credentials: _AirbyteCredentials,
172        name: str | None = None,
173        workspace_id: str | None = None,
174    ) -> None:
175        """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`."""
176        self.connector_id = connector_id
177        """The connector ID."""
178
179        self.workspace_id = workspace_id
180        """The workspace ID."""
181
182        self._credentials = credentials
183        self._name = name
184        self._details: AgentConnectorDetails | None = None
185
186    @property
187    def name(self) -> str | None:
188        """The connector name, fetched from the Agents API if not already known."""
189        if self._name is None:
190            self._name = self.inspect().name
191        return self._name
192
193    def inspect(self, *, force_refresh: bool = False) -> AgentConnectorDetails:
194        """Return connector metadata from the Agents API `inspect` endpoint.
195
196        The result is cached; pass `force_refresh=True` to fetch it again.
197        """
198        if self._details is None or force_refresh:
199            self._details = AgentConnectorDetails.model_validate(
200                _api_util.inspect_agent_connector(
201                    connector_id=self.connector_id,
202                    credentials=self._credentials,
203                    organization_id=self._credentials.organization_id,
204                )
205            )
206            self._name = self._details.name or self._name
207        return self._details
208
209    def execute(  # noqa: PLR0913  # Explicit args are the point of this public API.
210        self,
211        entity_type: str,
212        action: AgentAction | str,
213        api_args: dict[str, Any] | None = None,
214        *,
215        select_fields: list[str] | None = None,
216        exclude_fields: list[str] | None = None,
217        page_size: int | None = None,
218        cursor: str | None = None,
219        workspace_id: str | None = None,
220        skip_truncation: bool = True,
221        intent: str | None = None,
222    ) -> AgentExecuteResult:
223        """Execute a single action against one entity type on this connector.
224
225        `entity_type` and `action` are connector-specific, for example `issues` and `list`.
226        Use `inspect()` to see what a connector supports.
227
228        `api_args` holds connector-specific arguments passed through to the connector, for
229        example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted
230        by PyAirbyte or by the Agents API itself:
231
232        - `select_fields` and `exclude_fields` prune fields from returned entities.
233        - `page_size` and `cursor` are merged into `api_args` as pagination arguments. Context
234          Store `search` uses `limit` and `cursor`, `sql_select` uses the top-level `cursor`, and
235          direct connector actions use their own pagination arguments in `api_args`. Pass the
236          result cursor according to the action type.
237        - `workspace_id` selects the workspace an action runs against. It defaults to the
238          connector's workspace and is currently sent for `sql_select`, the only action the
239          Agents API scopes by workspace; direct connector actions are scoped by the connector.
240        - `skip_truncation` disables the Agents API's default truncation of large payloads.
241        - `intent` is a free-text description of why the action is being run, which some
242          connectors use to refine results.
243
244        The `download` action is rejected, because it returns a binary stream and PyAirbyte
245        does not yet support streaming responses.
246        """
247        if action in UNSUPPORTED_ACTIONS:
248            raise PyAirbyteInputError(
249                message=f"The {action!r} action is not supported by PyAirbyte.",
250                guidance=(
251                    "This action returns a binary stream instead of JSON, and PyAirbyte does "
252                    "not yet support streaming responses."
253                ),
254                context={"entity_type": entity_type, "action": action},
255            )
256
257        if action not in {*AgentReadAction, *AgentWriteAction}:
258            action_names = ", ".join(
259                member.value for member in (*AgentReadAction, *AgentWriteAction)
260            )
261            raise PyAirbyteInputError(
262                message=f"The {action!r} action is not a valid action name for `execute`.",
263                guidance=f"Use one of: {action_names}.",
264                context={"entity_type": entity_type, "action": action},
265            )
266
267        action_value = action.value if isinstance(action, Enum) else action
268        params = _build_params(api_args=api_args, page_size=page_size, cursor=cursor)
269        if action_value == AgentReadAction.SQL_SELECT.value:
270            resolved_workspace_id = workspace_id or self.workspace_id
271            if (
272                resolved_workspace_id is not None
273                and params.get("workspace_id") is None
274                and params.get("workspace_name") is None
275            ):
276                params.pop("workspace_name", None)
277                params["workspace_id"] = resolved_workspace_id
278        request_body: dict[str, Any] = {
279            "entity": entity_type,
280            "action": action_value,
281            "params": params,
282            "skip_truncation": skip_truncation,
283        }
284        if select_fields is not None:
285            request_body["select_fields"] = select_fields
286        if exclude_fields is not None:
287            request_body["exclude_fields"] = exclude_fields
288        if intent is not None:
289            request_body["intent"] = intent
290
291        return AgentExecuteResult.model_validate(
292            _api_util.execute_agent_connector_action(
293                connector_id=self.connector_id,
294                request_body=request_body,
295                credentials=self._credentials,
296                organization_id=self._credentials.organization_id,
297            )
298        )
299
300    def list_entities(
301        self,
302        entity_type: str,
303        api_args: dict[str, Any] | None = None,
304        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
305    ) -> AgentExecuteResult:
306        """Run the `list` action, which returns a page of entities of `entity_type`."""
307        return self.execute(entity_type, "list", api_args, **kwargs)
308
309    def iter_entities(
310        self,
311        entity_type: str,
312        api_args: dict[str, Any] | None = None,
313        *,
314        limit: int | None = None,
315        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `list_entities()`.
316    ) -> Iterator[dict[str, Any]]:
317        """Yield entities of `entity_type`, following the connector's pagination cursor.
318
319        This is the pagination-free way to read entities: each page is fetched lazily as
320        the caller iterates, so no cursor bookkeeping is needed.
321
322        ```python
323        for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
324            print(issue["title"])
325        ```
326
327        `limit` caps how many entities are yielded in total, which matters for entity types
328        with no natural end. Pass `page_size` to control how many are fetched per request.
329
330        Iteration stops early if the connector reports another page without advancing its
331        cursor, rather than requesting the same page forever.
332
333        Use `list_entities()` instead when a single page is enough, or when the result's
334        `status`, `warning`, or `execution_metadata` are needed.
335        """
336        cursor: str | None = kwargs.pop("cursor", None)
337        seen_cursors: set[str] = set()
338        yielded = 0
339
340        while True:
341            result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs)
342            for agent_entity in result.entities:
343                yield agent_entity
344                yielded += 1
345                if limit is not None and yielded >= limit:
346                    return
347
348            cursor = result.end_cursor
349            if not result.has_next_page or cursor is None or cursor in seen_cursors:
350                return
351            seen_cursors.add(cursor)
352
353    def search_entities(
354        self,
355        entity_type: str,
356        api_args: dict[str, Any] | None = None,
357        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
358    ) -> AgentExecuteResult:
359        """Run the `search` action, which returns matching entities of `entity_type`."""
360        return self.execute(entity_type, "search", api_args, **kwargs)
361
362    def get_entity(
363        self,
364        entity_type: str,
365        api_args: dict[str, Any] | None = None,
366        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
367    ) -> AgentExecuteResult:
368        """Run the `get` action, which returns a single entity of `entity_type`."""
369        return self.execute(entity_type, "get", api_args, **kwargs)
370
371    def create_entity(
372        self,
373        entity_type: str,
374        api_args: dict[str, Any] | None = None,
375        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
376    ) -> AgentExecuteResult:
377        """Run the `create` action, which creates an entity of `entity_type`."""
378        return self.execute(entity_type, "create", api_args, **kwargs)
379
380    def update_entity(
381        self,
382        entity_type: str,
383        api_args: dict[str, Any] | None = None,
384        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
385    ) -> AgentExecuteResult:
386        """Run the `update` action, which updates an entity of `entity_type`."""
387        return self.execute(entity_type, "update", api_args, **kwargs)
388
389    def delete_entity(
390        self,
391        entity_type: str,
392        api_args: dict[str, Any] | None = None,
393        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
394    ) -> AgentExecuteResult:
395        """Run the `delete` action, which deletes an entity of `entity_type`."""
396        return self.execute(entity_type, "delete", api_args, **kwargs)
397
398
399def _build_params(
400    *,
401    api_args: dict[str, Any] | None,
402    page_size: int | None,
403    cursor: str | None,
404) -> dict[str, Any]:
405    """Merge the pagination conveniences into the connector-specific `api_args`."""
406    params: dict[str, Any] = dict(api_args or {})
407    pagination: dict[str, Any] = {"page_size": page_size, "cursor": cursor}
408
409    conflicts = sorted(
410        name
411        for name, param_key in _PAGINATION_ARGS.items()
412        if pagination[name] is not None and param_key in params
413    )
414    if conflicts:
415        raise PyAirbyteInputError(
416            message="Pagination arguments were provided twice.",
417            guidance=(
418                "Pass each of `page_size` and `cursor` either as a keyword argument or "
419                "within `api_args`, but not both. Note that `page_size` is sent to the "
420                "connector as `limit`."
421            ),
422            context={"duplicated_args": conflicts},
423        )
424
425    params.update(
426        {
427            param_key: pagination[name]
428            for name, param_key in _PAGINATION_ARGS.items()
429            if pagination[name] is not None
430        }
431    )
432    return params
UNSUPPORTED_ACTIONS: set[str] = {'download'}

Actions PyAirbyte rejects before sending them to the Agents API.

download returns a binary stream rather than JSON, and PyAirbyte does not yet support streaming responses, so it is rejected with actionable guidance instead of failing later inside the transport layer.

class AgentReadAction(builtins.str, enum.Enum):
37class AgentReadAction(str, Enum):
38    """Connector actions that only read data.
39
40    The `search` action is the connector's native API search, parallel to `get` and `list`.
41    The `sql_select` action runs one read-only SQL statement (or `SHOW TABLES`) on the query
42    engine behind a destination connector. Pass `sql` and `sql_dialect` (and optionally
43    `dry_run`) in `api_args`; `entity_type` is ignored for this action.
44
45    The `download` action is deliberately absent even though it reads: it returns a binary
46    stream rather than JSON, which PyAirbyte does not yet support.
47    """
48
49    LIST = "list"
50    GET = "get"
51    SEARCH = "search"
52    SQL_SELECT = "sql_select"

Connector actions that only read data.

The search action is the connector's native API search, parallel to get and list. The sql_select action runs one read-only SQL statement (or SHOW TABLES) on the query engine behind a destination connector. Pass sql and sql_dialect (and optionally dry_run) in api_args; entity_type is ignored for this action.

The download action is deliberately absent even though it reads: it returns a binary stream rather than JSON, which PyAirbyte does not yet support.

LIST = <AgentReadAction.LIST: 'list'>
GET = <AgentReadAction.GET: 'get'>
SEARCH = <AgentReadAction.SEARCH: 'search'>
SQL_SELECT = <AgentReadAction.SQL_SELECT: 'sql_select'>
class AgentWriteAction(builtins.str, enum.Enum):
55class AgentWriteAction(str, Enum):
56    """Connector actions that create, update, or delete data."""
57
58    CREATE = "create"
59    UPDATE = "update"
60    DELETE = "delete"

Connector actions that create, update, or delete data.

CREATE = <AgentWriteAction.CREATE: 'create'>
UPDATE = <AgentWriteAction.UPDATE: 'update'>
DELETE = <AgentWriteAction.DELETE: 'delete'>

Every connector action accepted by AgentConnector.execute().

class AgentConnector:
152class AgentConnector:
153    """A connector in an Airbyte Agents workspace.
154
155    Get one from `AgentWorkspace.get_connector()` rather than constructing it directly.
156
157    ```python
158    from airbyte import agents
159
160    workspace = agents.AgentWorkspace.from_env()
161    connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
162    result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
163    for entity in result.entities:
164        print(entity["title"])
165    ```
166    """
167
168    def __init__(
169        self,
170        connector_id: str,
171        *,
172        credentials: _AirbyteCredentials,
173        name: str | None = None,
174        workspace_id: str | None = None,
175    ) -> None:
176        """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`."""
177        self.connector_id = connector_id
178        """The connector ID."""
179
180        self.workspace_id = workspace_id
181        """The workspace ID."""
182
183        self._credentials = credentials
184        self._name = name
185        self._details: AgentConnectorDetails | None = None
186
187    @property
188    def name(self) -> str | None:
189        """The connector name, fetched from the Agents API if not already known."""
190        if self._name is None:
191            self._name = self.inspect().name
192        return self._name
193
194    def inspect(self, *, force_refresh: bool = False) -> AgentConnectorDetails:
195        """Return connector metadata from the Agents API `inspect` endpoint.
196
197        The result is cached; pass `force_refresh=True` to fetch it again.
198        """
199        if self._details is None or force_refresh:
200            self._details = AgentConnectorDetails.model_validate(
201                _api_util.inspect_agent_connector(
202                    connector_id=self.connector_id,
203                    credentials=self._credentials,
204                    organization_id=self._credentials.organization_id,
205                )
206            )
207            self._name = self._details.name or self._name
208        return self._details
209
210    def execute(  # noqa: PLR0913  # Explicit args are the point of this public API.
211        self,
212        entity_type: str,
213        action: AgentAction | str,
214        api_args: dict[str, Any] | None = None,
215        *,
216        select_fields: list[str] | None = None,
217        exclude_fields: list[str] | None = None,
218        page_size: int | None = None,
219        cursor: str | None = None,
220        workspace_id: str | None = None,
221        skip_truncation: bool = True,
222        intent: str | None = None,
223    ) -> AgentExecuteResult:
224        """Execute a single action against one entity type on this connector.
225
226        `entity_type` and `action` are connector-specific, for example `issues` and `list`.
227        Use `inspect()` to see what a connector supports.
228
229        `api_args` holds connector-specific arguments passed through to the connector, for
230        example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted
231        by PyAirbyte or by the Agents API itself:
232
233        - `select_fields` and `exclude_fields` prune fields from returned entities.
234        - `page_size` and `cursor` are merged into `api_args` as pagination arguments. Context
235          Store `search` uses `limit` and `cursor`, `sql_select` uses the top-level `cursor`, and
236          direct connector actions use their own pagination arguments in `api_args`. Pass the
237          result cursor according to the action type.
238        - `workspace_id` selects the workspace an action runs against. It defaults to the
239          connector's workspace and is currently sent for `sql_select`, the only action the
240          Agents API scopes by workspace; direct connector actions are scoped by the connector.
241        - `skip_truncation` disables the Agents API's default truncation of large payloads.
242        - `intent` is a free-text description of why the action is being run, which some
243          connectors use to refine results.
244
245        The `download` action is rejected, because it returns a binary stream and PyAirbyte
246        does not yet support streaming responses.
247        """
248        if action in UNSUPPORTED_ACTIONS:
249            raise PyAirbyteInputError(
250                message=f"The {action!r} action is not supported by PyAirbyte.",
251                guidance=(
252                    "This action returns a binary stream instead of JSON, and PyAirbyte does "
253                    "not yet support streaming responses."
254                ),
255                context={"entity_type": entity_type, "action": action},
256            )
257
258        if action not in {*AgentReadAction, *AgentWriteAction}:
259            action_names = ", ".join(
260                member.value for member in (*AgentReadAction, *AgentWriteAction)
261            )
262            raise PyAirbyteInputError(
263                message=f"The {action!r} action is not a valid action name for `execute`.",
264                guidance=f"Use one of: {action_names}.",
265                context={"entity_type": entity_type, "action": action},
266            )
267
268        action_value = action.value if isinstance(action, Enum) else action
269        params = _build_params(api_args=api_args, page_size=page_size, cursor=cursor)
270        if action_value == AgentReadAction.SQL_SELECT.value:
271            resolved_workspace_id = workspace_id or self.workspace_id
272            if (
273                resolved_workspace_id is not None
274                and params.get("workspace_id") is None
275                and params.get("workspace_name") is None
276            ):
277                params.pop("workspace_name", None)
278                params["workspace_id"] = resolved_workspace_id
279        request_body: dict[str, Any] = {
280            "entity": entity_type,
281            "action": action_value,
282            "params": params,
283            "skip_truncation": skip_truncation,
284        }
285        if select_fields is not None:
286            request_body["select_fields"] = select_fields
287        if exclude_fields is not None:
288            request_body["exclude_fields"] = exclude_fields
289        if intent is not None:
290            request_body["intent"] = intent
291
292        return AgentExecuteResult.model_validate(
293            _api_util.execute_agent_connector_action(
294                connector_id=self.connector_id,
295                request_body=request_body,
296                credentials=self._credentials,
297                organization_id=self._credentials.organization_id,
298            )
299        )
300
301    def list_entities(
302        self,
303        entity_type: str,
304        api_args: dict[str, Any] | None = None,
305        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
306    ) -> AgentExecuteResult:
307        """Run the `list` action, which returns a page of entities of `entity_type`."""
308        return self.execute(entity_type, "list", api_args, **kwargs)
309
310    def iter_entities(
311        self,
312        entity_type: str,
313        api_args: dict[str, Any] | None = None,
314        *,
315        limit: int | None = None,
316        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `list_entities()`.
317    ) -> Iterator[dict[str, Any]]:
318        """Yield entities of `entity_type`, following the connector's pagination cursor.
319
320        This is the pagination-free way to read entities: each page is fetched lazily as
321        the caller iterates, so no cursor bookkeeping is needed.
322
323        ```python
324        for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
325            print(issue["title"])
326        ```
327
328        `limit` caps how many entities are yielded in total, which matters for entity types
329        with no natural end. Pass `page_size` to control how many are fetched per request.
330
331        Iteration stops early if the connector reports another page without advancing its
332        cursor, rather than requesting the same page forever.
333
334        Use `list_entities()` instead when a single page is enough, or when the result's
335        `status`, `warning`, or `execution_metadata` are needed.
336        """
337        cursor: str | None = kwargs.pop("cursor", None)
338        seen_cursors: set[str] = set()
339        yielded = 0
340
341        while True:
342            result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs)
343            for agent_entity in result.entities:
344                yield agent_entity
345                yielded += 1
346                if limit is not None and yielded >= limit:
347                    return
348
349            cursor = result.end_cursor
350            if not result.has_next_page or cursor is None or cursor in seen_cursors:
351                return
352            seen_cursors.add(cursor)
353
354    def search_entities(
355        self,
356        entity_type: str,
357        api_args: dict[str, Any] | None = None,
358        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
359    ) -> AgentExecuteResult:
360        """Run the `search` action, which returns matching entities of `entity_type`."""
361        return self.execute(entity_type, "search", api_args, **kwargs)
362
363    def get_entity(
364        self,
365        entity_type: str,
366        api_args: dict[str, Any] | None = None,
367        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
368    ) -> AgentExecuteResult:
369        """Run the `get` action, which returns a single entity of `entity_type`."""
370        return self.execute(entity_type, "get", api_args, **kwargs)
371
372    def create_entity(
373        self,
374        entity_type: str,
375        api_args: dict[str, Any] | None = None,
376        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
377    ) -> AgentExecuteResult:
378        """Run the `create` action, which creates an entity of `entity_type`."""
379        return self.execute(entity_type, "create", api_args, **kwargs)
380
381    def update_entity(
382        self,
383        entity_type: str,
384        api_args: dict[str, Any] | None = None,
385        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
386    ) -> AgentExecuteResult:
387        """Run the `update` action, which updates an entity of `entity_type`."""
388        return self.execute(entity_type, "update", api_args, **kwargs)
389
390    def delete_entity(
391        self,
392        entity_type: str,
393        api_args: dict[str, Any] | None = None,
394        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
395    ) -> AgentExecuteResult:
396        """Run the `delete` action, which deletes an entity of `entity_type`."""
397        return self.execute(entity_type, "delete", api_args, **kwargs)

A connector in an Airbyte Agents workspace.

Get one from AgentWorkspace.get_connector() rather than constructing it directly.

from airbyte import agents

workspace = agents.AgentWorkspace.from_env()
connector = workspace.get_connector("GitHub")  # by ID or name (case insensitive)
result = connector.list_entities("issues", api_args={"repository": "airbytehq/PyAirbyte"})
for entity in result.entities:
    print(entity["title"])
AgentConnector( connector_id: str, *, credentials: airbyte.cloud._credentials._AirbyteCredentials, name: str | None = None, workspace_id: str | None = None)
168    def __init__(
169        self,
170        connector_id: str,
171        *,
172        credentials: _AirbyteCredentials,
173        name: str | None = None,
174        workspace_id: str | None = None,
175    ) -> None:
176        """Initialize an `AgentConnector`. Prefer `AgentWorkspace.get_connector()`."""
177        self.connector_id = connector_id
178        """The connector ID."""
179
180        self.workspace_id = workspace_id
181        """The workspace ID."""
182
183        self._credentials = credentials
184        self._name = name
185        self._details: AgentConnectorDetails | None = None

Initialize an AgentConnector. Prefer AgentWorkspace.get_connector().

connector_id

The connector ID.

workspace_id

The workspace ID.

name: str | None
187    @property
188    def name(self) -> str | None:
189        """The connector name, fetched from the Agents API if not already known."""
190        if self._name is None:
191            self._name = self.inspect().name
192        return self._name

The connector name, fetched from the Agents API if not already known.

def inspect( self, *, force_refresh: bool = False) -> airbyte.agents.AgentConnectorDetails:
194    def inspect(self, *, force_refresh: bool = False) -> AgentConnectorDetails:
195        """Return connector metadata from the Agents API `inspect` endpoint.
196
197        The result is cached; pass `force_refresh=True` to fetch it again.
198        """
199        if self._details is None or force_refresh:
200            self._details = AgentConnectorDetails.model_validate(
201                _api_util.inspect_agent_connector(
202                    connector_id=self.connector_id,
203                    credentials=self._credentials,
204                    organization_id=self._credentials.organization_id,
205                )
206            )
207            self._name = self._details.name or self._name
208        return self._details

Return connector metadata from the Agents API inspect endpoint.

The result is cached; pass force_refresh=True to fetch it again.

def execute( self, entity_type: str, action: AgentReadAction | AgentWriteAction | str, api_args: dict[str, typing.Any] | None = None, *, select_fields: list[str] | None = None, exclude_fields: list[str] | None = None, page_size: int | None = None, cursor: str | None = None, workspace_id: str | None = None, skip_truncation: bool = True, intent: str | None = None) -> airbyte.agents.AgentExecuteResult:
210    def execute(  # noqa: PLR0913  # Explicit args are the point of this public API.
211        self,
212        entity_type: str,
213        action: AgentAction | str,
214        api_args: dict[str, Any] | None = None,
215        *,
216        select_fields: list[str] | None = None,
217        exclude_fields: list[str] | None = None,
218        page_size: int | None = None,
219        cursor: str | None = None,
220        workspace_id: str | None = None,
221        skip_truncation: bool = True,
222        intent: str | None = None,
223    ) -> AgentExecuteResult:
224        """Execute a single action against one entity type on this connector.
225
226        `entity_type` and `action` are connector-specific, for example `issues` and `list`.
227        Use `inspect()` to see what a connector supports.
228
229        `api_args` holds connector-specific arguments passed through to the connector, for
230        example `{"repository": "airbytehq/PyAirbyte"}`. All other arguments are interpreted
231        by PyAirbyte or by the Agents API itself:
232
233        - `select_fields` and `exclude_fields` prune fields from returned entities.
234        - `page_size` and `cursor` are merged into `api_args` as pagination arguments. Context
235          Store `search` uses `limit` and `cursor`, `sql_select` uses the top-level `cursor`, and
236          direct connector actions use their own pagination arguments in `api_args`. Pass the
237          result cursor according to the action type.
238        - `workspace_id` selects the workspace an action runs against. It defaults to the
239          connector's workspace and is currently sent for `sql_select`, the only action the
240          Agents API scopes by workspace; direct connector actions are scoped by the connector.
241        - `skip_truncation` disables the Agents API's default truncation of large payloads.
242        - `intent` is a free-text description of why the action is being run, which some
243          connectors use to refine results.
244
245        The `download` action is rejected, because it returns a binary stream and PyAirbyte
246        does not yet support streaming responses.
247        """
248        if action in UNSUPPORTED_ACTIONS:
249            raise PyAirbyteInputError(
250                message=f"The {action!r} action is not supported by PyAirbyte.",
251                guidance=(
252                    "This action returns a binary stream instead of JSON, and PyAirbyte does "
253                    "not yet support streaming responses."
254                ),
255                context={"entity_type": entity_type, "action": action},
256            )
257
258        if action not in {*AgentReadAction, *AgentWriteAction}:
259            action_names = ", ".join(
260                member.value for member in (*AgentReadAction, *AgentWriteAction)
261            )
262            raise PyAirbyteInputError(
263                message=f"The {action!r} action is not a valid action name for `execute`.",
264                guidance=f"Use one of: {action_names}.",
265                context={"entity_type": entity_type, "action": action},
266            )
267
268        action_value = action.value if isinstance(action, Enum) else action
269        params = _build_params(api_args=api_args, page_size=page_size, cursor=cursor)
270        if action_value == AgentReadAction.SQL_SELECT.value:
271            resolved_workspace_id = workspace_id or self.workspace_id
272            if (
273                resolved_workspace_id is not None
274                and params.get("workspace_id") is None
275                and params.get("workspace_name") is None
276            ):
277                params.pop("workspace_name", None)
278                params["workspace_id"] = resolved_workspace_id
279        request_body: dict[str, Any] = {
280            "entity": entity_type,
281            "action": action_value,
282            "params": params,
283            "skip_truncation": skip_truncation,
284        }
285        if select_fields is not None:
286            request_body["select_fields"] = select_fields
287        if exclude_fields is not None:
288            request_body["exclude_fields"] = exclude_fields
289        if intent is not None:
290            request_body["intent"] = intent
291
292        return AgentExecuteResult.model_validate(
293            _api_util.execute_agent_connector_action(
294                connector_id=self.connector_id,
295                request_body=request_body,
296                credentials=self._credentials,
297                organization_id=self._credentials.organization_id,
298            )
299        )

Execute a single action against one entity type on this connector.

entity_type and action are connector-specific, for example issues and list. Use inspect() to see what a connector supports.

api_args holds connector-specific arguments passed through to the connector, for example {"repository": "airbytehq/PyAirbyte"}. All other arguments are interpreted by PyAirbyte or by the Agents API itself:

  • select_fields and exclude_fields prune fields from returned entities.
  • page_size and cursor are merged into api_args as pagination arguments. Context Store search uses limit and cursor, sql_select uses the top-level cursor, and direct connector actions use their own pagination arguments in api_args. Pass the result cursor according to the action type.
  • workspace_id selects the workspace an action runs against. It defaults to the connector's workspace and is currently sent for sql_select, the only action the Agents API scopes by workspace; direct connector actions are scoped by the connector.
  • skip_truncation disables the Agents API's default truncation of large payloads.
  • intent is a free-text description of why the action is being run, which some connectors use to refine results.

The download action is rejected, because it returns a binary stream and PyAirbyte does not yet support streaming responses.

def list_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
301    def list_entities(
302        self,
303        entity_type: str,
304        api_args: dict[str, Any] | None = None,
305        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
306    ) -> AgentExecuteResult:
307        """Run the `list` action, which returns a page of entities of `entity_type`."""
308        return self.execute(entity_type, "list", api_args, **kwargs)

Run the list action, which returns a page of entities of entity_type.

def iter_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, *, limit: int | None = None, **kwargs: Any) -> Iterator[dict[str, typing.Any]]:
310    def iter_entities(
311        self,
312        entity_type: str,
313        api_args: dict[str, Any] | None = None,
314        *,
315        limit: int | None = None,
316        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `list_entities()`.
317    ) -> Iterator[dict[str, Any]]:
318        """Yield entities of `entity_type`, following the connector's pagination cursor.
319
320        This is the pagination-free way to read entities: each page is fetched lazily as
321        the caller iterates, so no cursor bookkeeping is needed.
322
323        ```python
324        for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
325            print(issue["title"])
326        ```
327
328        `limit` caps how many entities are yielded in total, which matters for entity types
329        with no natural end. Pass `page_size` to control how many are fetched per request.
330
331        Iteration stops early if the connector reports another page without advancing its
332        cursor, rather than requesting the same page forever.
333
334        Use `list_entities()` instead when a single page is enough, or when the result's
335        `status`, `warning`, or `execution_metadata` are needed.
336        """
337        cursor: str | None = kwargs.pop("cursor", None)
338        seen_cursors: set[str] = set()
339        yielded = 0
340
341        while True:
342            result = self.list_entities(entity_type, api_args, cursor=cursor, **kwargs)
343            for agent_entity in result.entities:
344                yield agent_entity
345                yielded += 1
346                if limit is not None and yielded >= limit:
347                    return
348
349            cursor = result.end_cursor
350            if not result.has_next_page or cursor is None or cursor in seen_cursors:
351                return
352            seen_cursors.add(cursor)

Yield entities of entity_type, following the connector's pagination cursor.

This is the pagination-free way to read entities: each page is fetched lazily as the caller iterates, so no cursor bookkeeping is needed.

for issue in connector.iter_entities("issues", {"repository": "airbytehq/PyAirbyte"}):
    print(issue["title"])

limit caps how many entities are yielded in total, which matters for entity types with no natural end. Pass page_size to control how many are fetched per request.

Iteration stops early if the connector reports another page without advancing its cursor, rather than requesting the same page forever.

Use list_entities() instead when a single page is enough, or when the result's status, warning, or execution_metadata are needed.

def search_entities( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
354    def search_entities(
355        self,
356        entity_type: str,
357        api_args: dict[str, Any] | None = None,
358        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
359    ) -> AgentExecuteResult:
360        """Run the `search` action, which returns matching entities of `entity_type`."""
361        return self.execute(entity_type, "search", api_args, **kwargs)

Run the search action, which returns matching entities of entity_type.

def get_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
363    def get_entity(
364        self,
365        entity_type: str,
366        api_args: dict[str, Any] | None = None,
367        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
368    ) -> AgentExecuteResult:
369        """Run the `get` action, which returns a single entity of `entity_type`."""
370        return self.execute(entity_type, "get", api_args, **kwargs)

Run the get action, which returns a single entity of entity_type.

def create_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
372    def create_entity(
373        self,
374        entity_type: str,
375        api_args: dict[str, Any] | None = None,
376        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
377    ) -> AgentExecuteResult:
378        """Run the `create` action, which creates an entity of `entity_type`."""
379        return self.execute(entity_type, "create", api_args, **kwargs)

Run the create action, which creates an entity of entity_type.

def update_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
381    def update_entity(
382        self,
383        entity_type: str,
384        api_args: dict[str, Any] | None = None,
385        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
386    ) -> AgentExecuteResult:
387        """Run the `update` action, which updates an entity of `entity_type`."""
388        return self.execute(entity_type, "update", api_args, **kwargs)

Run the update action, which updates an entity of entity_type.

def delete_entity( self, entity_type: str, api_args: dict[str, typing.Any] | None = None, **kwargs: Any) -> airbyte.agents.AgentExecuteResult:
390    def delete_entity(
391        self,
392        entity_type: str,
393        api_args: dict[str, Any] | None = None,
394        **kwargs: Any,  # noqa: ANN401  # Forwarded verbatim to `execute()`.
395    ) -> AgentExecuteResult:
396        """Run the `delete` action, which deletes an entity of `entity_type`."""
397        return self.execute(entity_type, "delete", api_args, **kwargs)

Run the delete action, which deletes an entity of entity_type.