Skip to content

Apps

Apps is your entry point into CX Agent Studio. It lets you list every app in a project, look one up by display name, create brand new ones, and move app definitions in and out through import and export. Think of it as the "file manager" for your CXAS workspace.

You'll reach for Apps directly whenever you want to work at the project level — for example, when writing a script that syncs apps between environments or backs up all your apps to GCS.

Note: Most other classes (Agents, Tools, Sessions, etc.) extend Apps or Common, so you rarely need to instantiate Apps on its own unless you specifically need project-level operations.

Quick Example

from cxas_scrapi import Apps

apps = Apps(
    project_id="my-gcp-project",
    location="us",
    creds_path="/path/to/service_account.json",
)

# List all apps
all_apps = apps.list_apps()
for app in all_apps:
    print(app.display_name, app.name)

# Find an app by display name
my_app = apps.get_app_by_display_name("My Support Agent")

# Export it to a local zip
if my_app:
    apps.export_app(my_app.name, local_path="backup.zip")

# Import it into another project
apps.import_app(
    app_name=my_app.name,
    local_path="backup.zip",
    conflict_strategy="REPLACE",
)

Reference

Apps

Apps(project_id, location, creds_path=None, creds_dict=None, creds=None, scope=None, **kwargs)

Bases: Common

Core Class for managing App Resources.

Source code in src/cxas_scrapi/core/apps.py
def __init__(
    self,
    project_id: str,
    location: str,
    creds_path: str = None,
    creds_dict: Dict[str, str] = None,
    creds: Any = None,
    scope: List[str] = None,
    **kwargs,
):
    super().__init__(
        creds_path=creds_path,
        creds_dict=creds_dict,
        creds=creds,
        scope=scope,
        **kwargs,
    )
    self.project_id = project_id
    self.location = location
    self.parent = f"projects/{project_id}/locations/{location}"

    self.client_options = self._get_client_options(self.parent)

    self.client = AgentServiceClient(
        transport=self.get_grpc_transport(AgentServiceClient),
        client_info=self.client_info
    )

list_apps

list_apps()

Lists apps in the configured project and location.

Source code in src/cxas_scrapi/core/apps.py
def list_apps(self) -> List[types.App]:
    """Lists apps in the configured project and location."""
    request = types.ListAppsRequest(parent=self.parent)
    response = self.client.list_apps(request=request)
    return list(response)

get_apps_map

get_apps_map(reverse=False)

Creates a map of App full names to display names.

Parameters:

Name Type Description Default
reverse bool

If True, map display_name -> name.

False
Source code in src/cxas_scrapi/core/apps.py
def get_apps_map(self, reverse: bool = False) -> Dict[str, str]:
    """Creates a map of App full names to display names.

    Args:
        reverse: If True, map display_name -> name.
    """
    apps = self.list_apps()
    apps_dict: Dict[str, str] = {}

    for app in apps:
        display_name = app.display_name
        name = app.name
        if display_name and name:
            if reverse:
                apps_dict[display_name] = name
            else:
                apps_dict[name] = display_name
    return apps_dict

get_app

get_app(app_name)

Gets a specific app by its full resource name.

Source code in src/cxas_scrapi/core/apps.py
def get_app(self, app_name: str) -> types.App:
    """Gets a specific app by its full resource name."""
    request = types.GetAppRequest(name=app_name)
    return self.client.get_app(request=request)

get_app_by_display_name

get_app_by_display_name(display_name)

Get CX Agent Studio App by its human readable display name.

Parameters:

Name Type Description Default
display_name str

human-readable display name of CX Agent Studio App as a string.

required

Returns:

Type Description
Optional[App]

CX Agent Studio App resource object. If no app is found,

Optional[App]

returns None.

Source code in src/cxas_scrapi/core/apps.py
def get_app_by_display_name(self, display_name: str) -> Optional[types.App]:
    """Get CX Agent Studio App by its human readable display name.

    Args:
        display_name: human-readable display name of CX Agent Studio App as
            a string.

    Returns:
        CX Agent Studio App resource object. If no app is found,
        returns None.
    """
    apps_list = self.list_apps()

    possible_app = None
    matched_app = None

    for app in apps_list:
        if app.display_name == display_name and not matched_app:
            matched_app = app
        elif app.display_name == display_name and matched_app:
            possible_app = app
        elif app.display_name.lower() == display_name.lower():
            possible_app = app

    if possible_app and not matched_app:
        logging.warning(
            'display_name is case-sensitive. Did you mean "%s"?',
            possible_app.display_name,
        )
    elif possible_app and matched_app:
        logging.warning(
            'Found multiple apps with the display name "%s".',
            possible_app.display_name,
        )
        matched_app = None

    return matched_app

create_app

create_app(app_id, display_name, description=None, root_agent=None)

Creates a new app.

Source code in src/cxas_scrapi/core/apps.py
def create_app(
    self,
    app_id: str,
    display_name: str,
    description: str = None,
    root_agent: str = None,
) -> types.App:
    """Creates a new app."""
    app = types.App(display_name=display_name)
    if description:
        app.description = description
    if root_agent:
        app.root_agent = root_agent

    request = types.CreateAppRequest(
        parent=self.parent, app=app, app_id=app_id
    )
    operation = self.client.create_app(request=request)
    return operation.result()

update_app

update_app(app_name, **kwargs)

Updates specific fields of an existing App.

Source code in src/cxas_scrapi/core/apps.py
def update_app(self, app_name: str, **kwargs) -> types.App:
    """Updates specific fields of an existing App."""
    app = types.App(name=app_name)
    mask_paths = []

    for key, value in kwargs.items():
        setattr(app, key, value)
        mask_paths.append(key)

    request = types.UpdateAppRequest(
        app=app, update_mask=field_mask_pb2.FieldMask(paths=mask_paths)
    )
    return self.client.update_app(request=request)

delete_app

delete_app(app_name, force=False)

Deletes a specific app.

Source code in src/cxas_scrapi/core/apps.py
def delete_app(self, app_name: str, force: bool = False) -> None:
    """Deletes a specific app."""
    request = types.DeleteAppRequest(name=app_name)
    self.client.delete_app(request=request)

export_app

export_app(app_name, gcs_uri=None, local_path=None, export_format='JSON')

Exports the specified app.

Parameters:

Name Type Description Default
app_name str

The full resource name of the app to export.

required
gcs_uri str

Optional. The Google Cloud Storage URI to export to.

None
local_path str

Optional. Local file path to write the exported zip archive.

None
export_format str

The format to export the app in ('JSON' or 'YAML').

'JSON'
Source code in src/cxas_scrapi/core/apps.py
def export_app(
    self,
    app_name: str,
    gcs_uri: str = None,
    local_path: str = None,
    export_format: str = "JSON",
) -> Any:
    # Wait for long-running operation to complete.
    """Exports the specified app.

    Args:
        app_name: The full resource name of the app to export.
        gcs_uri: Optional. The Google Cloud Storage URI to export to.
        local_path: Optional. Local file path to write the exported zip
            archive.
        export_format: The format to export the app in ('JSON' or 'YAML').
    """
    # Validate that exactly one source is provided if both are given
    if gcs_uri and local_path:
        raise ValueError(
            "Only one of 'gcs_uri' or 'local_path' can be provided."
        )

    request = types.ExportAppRequest(
        name=app_name,
        gcs_uri=gcs_uri if gcs_uri else None,
        export_format=export_format,
    )

    operation = self.client.export_app(request=request)

    if local_path:
        # We must wait for the result if writing to a local path
        response = operation.result()
        with open(local_path, "wb") as f:
            f.write(response.app_content)
        return response

    return operation

import_as_new_app

import_as_new_app(display_name, app_content=None, gcs_uri=None, local_path=None)

Imports an app as a brand new app.

The ZIP archive should contain a single top-level wrapper directory (e.g., App_Name/app.json, App_Name/agents/).

Parameters:

Name Type Description Default
display_name str

The display name for the new app.

required
app_content bytes

Optional. The raw bytes of the zip archive of the app.

None
gcs_uri str

Optional. The Google Cloud Storage URI to export to.

None
local_path str

Optional. The local path to the zip archive of the app.

None
Source code in src/cxas_scrapi/core/apps.py
def import_as_new_app(
    self,
    display_name: str,
    app_content: bytes = None,
    gcs_uri: str = None,
    local_path: str = None,
) -> Any:
    """Imports an app as a brand new app.

    The ZIP archive should contain a single top-level wrapper directory
    (e.g., `App_Name/app.json`, `App_Name/agents/`).

    Args:
        display_name: The display name for the new app.
        app_content: Optional. The raw bytes of the zip archive of the app.
        gcs_uri: Optional. The Google Cloud Storage URI to export to.
        local_path: Optional. The local path to the zip archive of the app.
    """
    # Validate that exactly one source is provided
    sources_provided = sum(
        [
            app_content is not None,
            gcs_uri is not None,
            local_path is not None,
        ]
    )
    if sources_provided != 1:
        raise ValueError(
            "Exactly one of 'app_content', 'gcs_uri', or 'local_path' "
            "must be provided."
        )

    request_kwargs = {
        "parent": self.parent,
        "display_name": display_name,
    }

    if local_path:
        with open(local_path, "rb") as f:
            request_kwargs["app_content"] = f.read()
    elif app_content:
        request_kwargs["app_content"] = app_content
    elif gcs_uri:
        request_kwargs["gcs_uri"] = gcs_uri

    request = types.ImportAppRequest(**request_kwargs)
    return self.client.import_app(request=request)

import_app

import_app(app_name, app_content=None, gcs_uri=None, local_path=None, conflict_strategy=None)

Imports an app, overwriting an existing one.

The ZIP archive should contain a single top-level wrapper directory (e.g., App_Name/app.json, App_Name/agents/).

Parameters:

Name Type Description Default
app_name str

Target App full resource name to explicitly overwrite.

required
app_content bytes

Optional. The raw bytes of the zip archive of the app.

None
gcs_uri str

Optional. The Google Cloud Storage URI to export to.

None
local_path str

Optional. The local path to the zip archive of the app.

None
conflict_strategy str

Optional. The conflict resolution strategy to use ('REPLACE' or 'OVERWRITE').

None
Source code in src/cxas_scrapi/core/apps.py
def import_app(
    self,
    app_name: str,
    app_content: bytes = None,
    gcs_uri: str = None,
    local_path: str = None,
    conflict_strategy: str = None,
) -> Any:
    # Wait for long-running operation to complete.
    """Imports an app, overwriting an existing one.

    The ZIP archive should contain a single top-level wrapper directory
    (e.g., `App_Name/app.json`, `App_Name/agents/`).

    Args:
        app_name: Target App full resource name to explicitly overwrite.
        app_content: Optional. The raw bytes of the zip archive of the app.
        gcs_uri: Optional. The Google Cloud Storage URI to export to.
        local_path: Optional. The local path to the zip archive of the app.
        conflict_strategy: Optional. The conflict resolution strategy to
            use ('REPLACE' or 'OVERWRITE').
    """
    # Validate that exactly one source is provided
    sources_provided = sum(
        [
            app_content is not None,
            gcs_uri is not None,
            local_path is not None,
        ]
    )
    if sources_provided != 1:
        raise ValueError(
            "Exactly one of 'app_content', 'gcs_uri', or 'local_path' "
            "must be provided."
        )

    # Extract the short ID if a full resource name is provided
    # format is: projects/{project_id}/locations/{location}/apps/{app_id}
    app_id_extracted = (
        app_name.rsplit("/", maxsplit=1)[-1]
        if "/" in app_name
        else app_name
    )

    request_kwargs = {
        "parent": self.parent,
        "app_id": app_id_extracted,
    }

    if local_path:
        with open(local_path, "rb") as f:
            request_kwargs["app_content"] = f.read()
    elif app_content:
        request_kwargs["app_content"] = app_content
    elif gcs_uri:
        request_kwargs["gcs_uri"] = gcs_uri

    if conflict_strategy:
        strategy_upper = conflict_strategy.upper()
        if strategy_upper not in ["REPLACE", "OVERWRITE"]:
            raise ValueError(
                "conflict_strategy must be either 'REPLACE' or 'OVERWRITE'"
            )

        strategy_enum = getattr(
            types.ImportAppRequest.ImportOptions.ConflictResolutionStrategy,
            strategy_upper,
        )
        request_kwargs["import_options"] = (
            types.ImportAppRequest.ImportOptions(
                conflict_resolution_strategy=strategy_enum
            )
        )
    else:
        # Maintain backward compatibility where app_id implied REPLACE
        request_kwargs["import_options"] = (
            types.ImportAppRequest.ImportOptions(
                conflict_resolution_strategy=types.ImportAppRequest.ImportOptions.ConflictResolutionStrategy.REPLACE
            )
        )

    request = types.ImportAppRequest(**request_kwargs)
    return self.client.import_app(request=request)