"""Arcade Starter Tools for hubspot_cms

DO NOT EDIT THIS MODULE DIRECTLY.

THIS MODULE WAS AUTO-GENERATED BY TRANSPILING THE API STARTER TOOL JSON DEFINITIONS
IN THE ../wrapper_tools DIRECTORY INTO PYTHON CODE. ANY CHANGES TO THIS MODULE WILL
BE OVERWRITTEN BY THE TRANSPILER.
"""

import asyncio
import json
from enum import Enum
from typing import Annotated, Any

import httpx
import jsonschema
from arcade_tdk import ToolContext, tool
from arcade_tdk.auth import OAuth2
from arcade_tdk.errors import RetryableToolError

from .request_body_schemas import REQUEST_BODY_SCHEMAS

# Retry configuration
INITIAL_RETRY_DELAY = 0.5  # seconds

HTTP_CLIENT = httpx.AsyncClient(
    timeout=httpx.Timeout(60.0, connect=10.0),
    limits=httpx.Limits(max_keepalive_connections=20, max_connections=100),
    transport=httpx.AsyncHTTPTransport(retries=3),
    http2=True,
    follow_redirects=True,
)


class ToolMode(str, Enum):
    """Mode for tools with complex request bodies."""

    GET_REQUEST_SCHEMA = "get_request_schema"
    EXECUTE = "execute"


def remove_none_values(data: dict[str, Any]) -> dict[str, Any]:
    return {k: v for k, v in data.items() if v is not None}


async def make_request(
    url: str,
    method: str,
    params: dict[str, Any] | None = None,
    headers: dict[str, Any] | None = None,
    content: str | None = None,
    data: dict[str, Any] | None = None,
    auth: tuple[str, str] | None = None,
    max_retries: int = 3,
) -> httpx.Response:
    """Make an HTTP request with retry logic for 5xx server errors."""
    for attempt in range(max_retries):
        try:
            response = await HTTP_CLIENT.request(
                url=url,
                auth=auth,
                method=method,
                params=params,
                headers=headers,
                content=content,
            )
            response.raise_for_status()
        except httpx.HTTPStatusError as e:
            # Only retry on 5xx server errors
            if e.response.status_code >= 500 and attempt < max_retries - 1:
                # Exponential backoff: 0.5s, 1s, 2s
                await asyncio.sleep(INITIAL_RETRY_DELAY * (2**attempt))
                continue
            # Re-raise for 4xx errors or if max retries reached
            raise
        except httpx.RequestError:
            # Don't retry request errors (network issues are handled by transport)
            raise
        else:
            return response

    # This should never be reached, but satisfies type checker
    raise httpx.RequestError("Max retries exceeded")  # noqa: TRY003


async def make_request_with_schema_validation(
    url: str,
    method: str,
    request_data: dict[str, Any],
    schema: dict[str, Any] | str,
    auth: tuple[str, str] | None = None,
    params: dict[str, Any] | None = None,
    headers: dict[str, Any] | None = None,
    max_retries: int = 3,
) -> httpx.Response:
    """Make an HTTP request with schema validation on format errors."""
    # Parse schema if it's a string, skip validation if parsing fails
    parsed_schema = None
    if isinstance(schema, str):
        try:
            parsed_schema = json.loads(schema)
        except Exception:
            # If schema parsing fails, just skip validation
            parsed_schema = None
    else:
        parsed_schema = schema

    try:
        response = await make_request(
            url=url,
            auth=auth,
            method=method,
            params=params,
            headers=headers,
            content=json.dumps(request_data),
            max_retries=max_retries,
        )
    except httpx.HTTPStatusError as e:
        # Only provide schema validation for format-related errors
        if e.response.status_code in (400, 422):
            api_error_details = f"API returned {e.response.status_code}: {e.response.text}"

            # Only run validation if we have a valid parsed schema
            if parsed_schema is not None:
                # Run validation to provide additional context
                is_valid, validation_error = validate_json_against_schema(
                    request_data, parsed_schema
                )

                if not is_valid:
                    # Schema validation found issues - additional context
                    additional_context = (
                        f"{api_error_details}\n\n"
                        f"Schema validation found the following issues:\n"
                        f"{validation_error}"
                    )
                else:
                    # Schema validation passed - just show API error
                    additional_context = api_error_details
            else:
                # No valid schema - just show API error
                additional_context = api_error_details

            raise RetryableToolError(
                message=(f"API request failed with validation error: {e.response.status_code}"),
                developer_message=api_error_details,
                additional_prompt_content=additional_context,
            ) from e
        else:
            # For non-validation errors, re-raise as-is
            raise
    else:
        return response


def validate_json_against_schema(
    json_data: dict[str, Any], schema: dict[str, Any]
) -> tuple[bool, str | None]:
    """Validate JSON data against an OpenAPI/JSON Schema.

    This provides full JSON Schema Draft 7 validation including:
    - Required fields, types, enums
    - Pattern validation (regex)
    - Format validation (email, uuid, date-time, etc.)
    - Min/max length and values
    - oneOf, anyOf, allOf
    - And all other JSON Schema features

    Args:
        json_data: The JSON data to validate
        schema: The JSON Schema to validate against

    Returns:
        Tuple of (is_valid, error_messages). If valid, error_messages is None.
        If invalid, error_messages contains all validation errors.
    """
    try:
        validator = jsonschema.Draft7Validator(
            schema, format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER
        )
        # Collect ALL validation errors
        errors = list(validator.iter_errors(json_data))
        if errors:
            # Format all errors with their paths
            error_messages = []
            for error in errors:
                error_path = ".".join(str(p) for p in error.path) if error.path else "root"
                error_messages.append(f"{error.message} at {error_path}")
            # Join all errors with newlines
            return False, "\n".join(error_messages)
        else:
            return True, None
    except jsonschema.SchemaError as e:
        return False, f"Invalid schema: {e.message}"
    except Exception as e:
        return False, f"Validation error: {e!s}"


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_author_by_id(
    context: ToolContext,
    blog_author_id: Annotated[
        str, "The unique identifier for the blog author to retrieve their details."
    ],
    include_deleted_blog_authors: Annotated[
        bool | None,
        "Set to true to include deleted blog authors in the results. Defaults to false.",
    ] = None,
    specific_author_property: Annotated[
        str | None,
        "Specify a property to retrieve specific details about the blog author, such as a specific attribute or field.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/authors/{objectId}_getById'."
]:
    """Retrieve blog author details using an object ID.

    This tool fetches details of a blog author from HubSpot CMS using the specified object ID. It should be called when specific information about a blog author is needed."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/{objectId}".format(  # noqa: UP032
            objectId=blog_author_id
        ),
        method="GET",
        params=remove_none_values({
            "archived": include_deleted_blog_authors,
            "property": specific_author_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_blog_author(
    context: ToolContext,
    blog_author_id: Annotated[str, "The unique identifier of the Blog Author to be deleted."],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived blog authors. Use for fetching deleted or inactive authors.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/blogs/authors/{objectId}_archive'.",
]:
    """Delete a specific blog author by ID.

    Use this tool to delete a blog author from the HubSpot CMS by providing their ID. It should be called when you need to remove an author from the system."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/{objectId}".format(  # noqa: UP032
            objectId=blog_author_id
        ),
        method="DELETE",
        params=remove_none_values({"archived": return_archived_only}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_author(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    blog_author_id: Annotated[
        str | None,
        "The unique ID of the blog author to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    update_deleted_blog_authors: Annotated[
        bool | None,
        "Set to `true` to update deleted Blog Authors. Defaults to `false`.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/blogs/authors/{objectId}_update'.",
]:
    """Update specific details of a blog author.

    This tool updates specific details of a blog author using their unique ID. It should be called when you need to modify existing author information without changing all attributes.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHOR"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not blog_author_id:
        missing_params.append(("blog_author_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHOR"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHOR"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors/{objectId}".format(  # noqa: UP032
            objectId=blog_author_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHOR"],
        params=remove_none_values({"archived": update_deleted_blog_authors}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_blog_author_from_lang_group(
    context: ToolContext,
    author_id_to_detach: Annotated[
        str, "ID of the blog author to remove from a multi-language group in HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/multi-language/detach-from-lang-group_detachFromLangGroup'.",  # noqa: E501
]:
    """Detach a Blog Author from a multi-language group.

    Use this tool to remove a blog author from an existing multi-language group in HubSpot CMS. Call this when authors need to be unlinked from specific language variations."""  # noqa: E501
    request_data: Any = {"id": author_id_to_detach}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_authors_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    update_deleted_blog_authors: Annotated[
        bool | None,
        "Set to true to update deleted Blog Authors. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/batch/update_updateBatch'.",
]:
    """Update multiple blog author objects at once.

    Use this tool to update multiple blog author objects in HubSpot CMS based on identifiers provided in the request.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORSBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORSBATCH"],
        params=remove_none_values({"archived": update_deleted_blog_authors}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_primary_language_blog_author(
    context: ToolContext,
    primary_language_author_id: Annotated[
        str, "The ID of the blog author to be set as the primary in a multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/blogs/authors/multi-language/set-new-lang-primary_setLangPrimary'.",  # noqa: E501
]:
    """Set a blog author as the primary language in a multilingual group.

    Use this tool to designate a specific blog author as the primary language author in a multi-language group within the HubSpot CMS."""  # noqa: E501
    request_data: Any = {"id": primary_language_author_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_blog_authors(
    context: ToolContext,
    blog_author_ids: Annotated[list[str], "List of blog author IDs to delete from HubSpot CMS."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/batch/archive_archiveBatch'.",
]:
    """Delete specified blog authors in HubSpot CMS.

    Use this tool to delete the Blog Author objects identified in the request body from HubSpot CMS."""  # noqa: E501
    request_data: Any = {"inputs": blog_author_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_authors_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/batch/create_createBatch'.",
]:
    """Create multiple blog authors in a single batch request.

    This tool creates multiple Blog Author objects based on the provided request details. It's useful for adding several blog authors at once to the HubSpot CMS.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORSBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORSBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_authors(
    context: ToolContext,
    author_identifiers: Annotated[
        list[str],
        "An array of strings representing the identifiers for the blog authors to retrieve.",
    ],
    include_deleted_authors: Annotated[
        bool | None,
        "Specifies whether to include deleted Blog Authors in the results. Set to true to include them.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/batch/read_readBatch'.",
]:
    """Retrieve specified Blog Author objects from the CMS.

    Use this tool to obtain detailed information about blog authors by specifying their identifiers. This is useful for accessing author details in the HubSpot CMS."""  # noqa: E501
    request_data: Any = {"inputs": author_identifiers}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/batch/read",
        method="POST",
        params=remove_none_values({"archived": include_deleted_authors}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_authors(
    context: ToolContext,
    created_after: Annotated[
        str | None, "Return only blog authors created after this specified timestamp."
    ] = None,
    created_at_exact_time: Annotated[
        str | None,
        "Return blog authors created at exactly the specified time. Use ISO 8601 format for timestamps.",  # noqa: E501
    ] = None,
    filter_by_creation_before_date: Annotated[
        str | None,
        "Return Blog Authors created before the specified date and time in ISO 8601 format.",
    ] = None,
    filter_by_last_updated_before: Annotated[
        str | None,
        "Only return blog authors last updated before the specified timestamp. Format: YYYY-MM-DDTHH:MM:SSZ.",  # noqa: E501
    ] = None,
    filter_by_updated_after: Annotated[
        str | None,
        "Return Blog Authors updated after a specific time. Use an ISO 8601 datetime string, e.g., '2023-01-01T00:00:00Z'.",  # noqa: E501
    ] = None,
    include_archived_authors: Annotated[
        bool | None,
        "Specify `true` to include archived (deleted) blog authors in the results. Defaults to `false`.",  # noqa: E501
    ] = None,
    included_properties: Annotated[
        str | None,
        "Comma-separated list of specific properties to include in the response for each author. If empty, all properties will be fetched by default.",  # noqa: E501
    ] = None,
    pagination_cursor_token: Annotated[
        str | None, "The cursor token from `paging.next.after` to fetch the next set of results."
    ] = None,
    result_limit: Annotated[
        int | None, "Maximum number of blog authors to return. Default is 100."
    ] = None,
    sort_fields: Annotated[
        list[str] | None,
        "Fields for sorting results. Choose from: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Default is `createdAt`.",  # noqa: E501
    ] = None,
    updated_at_timestamp: Annotated[
        str | None,
        "Return blog authors last updated at the specified exact time (ISO 8601 format).",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/authors_getPage'."
]:
    """Retrieve the list of blog authors.

    Use this tool to get a list of blog authors for integration, with support for paging and filtering. Useful for examining authors to suggest edits via external services."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors",
        method="GET",
        params=remove_none_values({
            "createdAt": created_at_exact_time,
            "createdAfter": created_after,
            "createdBefore": filter_by_creation_before_date,
            "updatedAt": updated_at_timestamp,
            "updatedAfter": filter_by_updated_after,
            "updatedBefore": filter_by_last_updated_before,
            "sort": sort_fields,
            "after": pagination_cursor_token,
            "limit": result_limit,
            "archived": include_archived_authors,
            "property": included_properties,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_author(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/authors_create'."
]:
    """Create a new Blog Author in HubSpot CMS.

    Use this tool to create a new blog author within the HubSpot CMS. It should be called when you need to add an author to your blog system.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHOR"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHOR"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHOR"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHOR"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_author_to_multilang_group(
    context: ToolContext,
    author_object_id: Annotated[
        str, "ID of the blog author to add to a multi-language group in HubSpot CMS."
    ],
    designated_language: Annotated[
        str,
        "The language code of the blog author to be added to a multi-language group, e.g., 'en' for English.",  # noqa: E501
    ],
    primary_language_object_id: Annotated[
        str, "ID of the primary language object in the multi-language group."
    ],
    primary_language: Annotated[
        str | None,
        "Specify the primary language of the multi-language group to which the author will be attached.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/multi-language/attach-to-lang-group_attachToLangGroup'.",  # noqa: E501
]:
    """Attach a Blog Author to a multi-language group.

    Use this tool to link a specific blog author with a multi-language group in HubSpot CMS. This action helps manage authors across different language variations of your blog."""  # noqa: E501
    request_data: Any = {
        "language": designated_language,
        "id": author_object_id,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/authors/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_author_language_variation(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/multi-language/create-language-variation_createLangVariation'.",  # noqa: E501
]:
    """Create a language variation for a blog author.

    This tool creates a new language variation from an existing blog author in HubSpot CMS, allowing for multilingual content management.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORLANGUAGEVARIATION"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORLANGUAGEVARIATION"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORLANGUAGEVARIATION"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors/multi-language/create-language-variation",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBLOGAUTHORLANGUAGEVARIATION"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_author_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/authors/multi-language/update-languages_updateLangs'.",  # noqa: E501
]:
    """Set new languages for each blog author in a group.

    Use this tool to explicitly update the languages associated with each blog author in a multi-language group.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORLANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORLANGUAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORLANGUAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/authors/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGAUTHORLANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_revision_history(
    context: ToolContext,
    blog_id: Annotated[
        str,
        "The unique identifier for the blog whose settings history is being retrieved. This is required for querying specific blog settings.",  # noqa: E501
    ],
    retrieve_revisions_before_timestamp: Annotated[
        str | None, "Retrieve revisions before this timestamp in milliseconds since Unix Epoch."
    ] = None,
    revision_limit: Annotated[
        int | None,
        "The maximum number of blog setting revisions to retrieve. Provide an integer value to restrict the number of results.",  # noqa: E501
    ] = None,
    start_date_for_revisions: Annotated[
        str | None,
        "Specify a date to filter revisions that occurred after this date. Format: YYYY-MM-DD.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/blog-settings/settings/{blogId}/revisions'.",
]:
    """Retrieve the revision history of blog settings.

    This tool retrieves the historical revisions for the settings of a specified blog, providing insights into past configurations."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/{blogId}/revisions".format(  # noqa: UP032
            blogId=blog_id
        ),
        method="GET",
        params=remove_none_values({
            "after": start_date_for_revisions,
            "before": retrieve_revisions_before_timestamp,
            "limit": revision_limit,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blog-settings/settings/multi-language/update-languages'.",  # noqa: E501
]:
    """Update the languages for blog settings.

    Use this tool to update the multi-language settings for blogs in HubSpot CMS. Call this tool when you need to modify the languages available for a blog.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGLANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGLANGUAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGLANGUAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGLANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_revision_details(
    context: ToolContext,
    blog_id: Annotated[
        str,
        "The unique identifier for the blog post. It is required to fetch the specific revision details.",  # noqa: E501
    ],
    revision_id: Annotated[
        str, "A unique string identifier for a specific blog revision to be retrieved."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/blog-settings/settings/{blogId}/revisions/{revisionId}'.",  # noqa: E501
]:
    """Retrieve specific blog revision details by ID.

    Fetches details of a specific blog revision using the blog and revision IDs. Useful for obtaining historical settings or changes of a blog post."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/{blogId}/revisions/{revisionId}".format(  # noqa: UP032
            blogId=blog_id, revisionId=revision_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_settings(
    context: ToolContext,
    blog_id: Annotated[
        str,
        "The unique identifier of the blog for which you want to fetch the settings. This is required to specify which blog's details to retrieve.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blog-settings/settings/{blogId}'."
]:
    """Fetch blog settings using a blog ID.

    This tool retrieves the settings for a blog identified by the provided blog ID from the HubSpot CMS. It should be called when specific details or configurations of a blog are needed."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/{blogId}".format(blogId=blog_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_language_variation(
    context: ToolContext,
    blog_id: Annotated[str, "ID of the blog to clone for creating a language variation."],
    blog_slug: Annotated[
        str | None,
        "Path to the blog. Used to specify the location of the language variation within the CMS.",
    ] = None,
    primary_language: Annotated[
        str | None,
        "Specify the language of the primary blog to clone. This determines the source content for the new language variation.",  # noqa: E501
    ] = None,
    target_language_for_blog_variant: Annotated[
        str | None,
        "The language code for the new blog variant, specifying the target language for translation.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blog-settings/settings/multi-language/create-language-variation'.",  # noqa: E501
]:
    """Create a language variation for a blog in HubSpot CMS.

    This tool allows you to create a new language variation for an existing blog using HubSpot CMS's multi-language settings. Use it when you need to support multiple languages for a blog."""  # noqa: E501
    request_data: Any = {
        "language": target_language_for_blog_variant,
        "id": blog_id,
        "primaryLanguage": primary_language,
        "slug": blog_slug,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/multi-language/create-language-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_settings(
    context: ToolContext,
    created_before: Annotated[
        str | None, "Retrieve blog settings created before this date. Format as 'YYYY-MM-DD'."
    ] = None,
    created_timestamp: Annotated[
        str | None,
        "The exact timestamp when the blog settings were created, formatted as an ISO 8601 string.",
    ] = None,
    filter_created_after_date: Annotated[
        str | None,
        "Filter settings to only include blogs created after the specified date (ISO 8601 format).",
    ] = None,
    include_archived: Annotated[
        bool | None, "Set to true to include archived blog settings and false to exclude them."
    ] = None,
    max_number_of_results: Annotated[
        int | None,
        "The maximum number of blog settings to retrieve. The value should be an integer.",
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A token to retrieve the next set of blog settings after the current page. Use this for paginating results.",  # noqa: E501
    ] = None,
    sort_options: Annotated[
        list[str] | None,
        "An array specifying fields to sort by, in order of priority (e.g., ['createdAt', '-updatedAt'] for ascending and descending).",  # noqa: E501
    ] = None,
    updated_after_timestamp: Annotated[
        str | None,
        "Fetch settings for blogs updated after this date and time. Use ISO 8601 format.",
    ] = None,
    updated_at_exact_timestamp: Annotated[
        str | None,
        "Fetch blogs with this exact update timestamp. Use ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ).",
    ] = None,
    updated_before_date: Annotated[
        str | None,
        "The latest date (inclusive) to filter blog updates. Provide in 'YYYY-MM-DD' format.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blog-settings/settings'."
]:
    """Retrieve current blog settings from the HubSpot CMS.

    Use this tool to fetch and return the current configuration and settings of blogs within the HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings",
        method="GET",
        params=remove_none_values({
            "createdAt": created_timestamp,
            "createdAfter": filter_created_after_date,
            "createdBefore": created_before,
            "updatedAt": updated_at_exact_timestamp,
            "updatedAfter": updated_after_timestamp,
            "updatedBefore": updated_before_date,
            "sort": sort_options,
            "after": pagination_cursor,
            "limit": max_number_of_results,
            "archived": include_archived,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_new_blog_primary_language(
    context: ToolContext,
    primary_language_object_id: Annotated[
        str, "ID of the blog object to set as primary in the multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/blog-settings/settings/multi-language/set-new-lang-primary'.",  # noqa: E501
]:
    """Set a new primary language for blogs.

    This tool is used to update the primary language for multi-language blog settings in HubSpot CMS. It should be called when there is a need to change the default language for blogs."""  # noqa: E501
    request_data: Any = {"id": primary_language_object_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_blog_from_language_group(
    context: ToolContext,
    object_id_to_detach: Annotated[
        str, "ID of the blog to remove from its multi-language group in HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blog-settings/settings/multi-language/detach-from-lang-group'.",  # noqa: E501
]:
    """Detach a blog from its multi-language group.

    Use this tool to separate a blog from its associated multi-language group settings in HubSpot CMS."""  # noqa: E501
    request_data: Any = {"id": object_id_to_detach}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_blog_to_language_group(
    context: ToolContext,
    designated_language: Annotated[
        str,
        "The language code of the blog to add to a multi-language group. Use standard language codes like 'en', 'fr', etc.",  # noqa: E501
    ],
    object_id_to_add: Annotated[
        str, "ID of the blog object to be added to a multi-language group in HubSpot CMS."
    ],
    primary_language_object_id: Annotated[
        str,
        "ID of the primary language object in the multi-language group to which the blog will be attached.",  # noqa: E501
    ],
    primary_language: Annotated[
        str | None,
        "Primary language code for the multi-language group to which the blog should be attached (e.g., 'en', 'fr').",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blog-settings/settings/multi-language/attach-to-lang-group'.",  # noqa: E501
]:
    """Attach a blog to a multi-language group in HubSpot CMS.

    Use this tool to attach a blog to a specified multi-language group within HubSpot CMS. It is helpful when managing blogs in different languages and ensuring they are correctly grouped."""  # noqa: E501
    request_data: Any = {
        "language": designated_language,
        "id": object_id_to_add,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blog-settings/settings/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_audit_logs(
    context: ToolContext,
    event_types: Annotated[
        list[str] | None,
        "A list of event types to filter by, such as CREATED, UPDATED, PUBLISHED, DELETED, UNPUBLISHED.",  # noqa: E501
    ] = None,
    filter_by_object_ids: Annotated[
        list[str] | None, "List of object IDs to filter the audit logs by."
    ] = None,
    filter_by_object_type: Annotated[
        list[str] | None,
        "List the object types to filter audit logs by, such as BLOG, LANDING_PAGE, DOMAIN, etc. Use a comma-separated format.",  # noqa: E501
    ] = None,
    number_of_logs_to_return: Annotated[
        int | None, "Specifies the number of audit logs to retrieve."
    ] = None,
    sort_direction: Annotated[
        list[str] | None,
        "Specify the sort direction for audit logs by timestamp, e.g., 'asc' or 'desc'.",
    ] = None,
    timestamp_after: Annotated[
        str | None, "Timestamp (in ISO 8601 format) after which audit logs will be returned."
    ] = None,
    timestamp_before: Annotated[
        str | None, "A timestamp to filter audit logs before the specified date and time."
    ] = None,
    user_ids_to_filter: Annotated[
        list[str] | None,
        "Array of user IDs to filter the audit logs by. Provide as a list of strings.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/cms/v3/audit-logs/_getPage'."]:
    """Retrieve audit logs based on specified filters.

    Use this tool to obtain audit logs from HubSpot CMS, filtered according to specific criteria. This is useful for tracking changes, monitoring activity, or performing security audits."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/audit-logs/",
        method="GET",
        params=remove_none_values({
            "userId": user_ids_to_filter,
            "eventType": event_types,
            "objectType": filter_by_object_type,
            "objectId": filter_by_object_ids,
            "after": timestamp_after,
            "before": timestamp_before,
            "limit": number_of_logs_to_return,
            "sort": sort_direction,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(
    requires_auth=OAuth2(
        id="arcade-hubspot",
        scopes=[
            "cms.knowledge_base.settings.read",
            "cms.domains.read",
            "cms.knowledge_base.settings.write",
            "content",
            "cms.domains.write",
        ],
    )
)
async def get_existing_domains(
    context: ToolContext,
    created_at_date_filter: Annotated[
        str | None,
        "Specify a date to return only domains created on this exact date. Format: YYYY-MM-DD.",
    ] = None,
    created_before_date: Annotated[
        str | None,
        "Filter to return only domains created before the specified date. Format: YYYY-MM-DD.",
    ] = None,
    filter_by_update_date: Annotated[
        str | None, "Only return domains updated on this specific date. Use 'YYYY-MM-DD' format."
    ] = None,
    filter_domains_updated_after: Annotated[
        str | None,
        "Return domains updated after this specified date. Use ISO 8601 format (YYYY-MM-DD).",
    ] = None,
    filter_updated_before_date: Annotated[
        str | None,
        "Return only domains updated before this date in ISO 8601 format (e.g., '2023-10-05T00:00:00Z').",  # noqa: E501
    ] = None,
    get_domains_created_after_date: Annotated[
        str | None, "Only return domains created after the specified date (in YYYY-MM-DD format)."
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "Specifies the maximum number of domain results to return per page."
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token of the last successfully read resource, used for paginated results.",  # noqa: E501
    ] = None,
    return_archived: Annotated[
        bool | None, "Return only archived domains if true. Return unarchived if false."
    ] = None,
    sort_criteria: Annotated[
        list[str] | None,
        "Defines the order of the domain results. Provide an array of strings specifying fields to sort by, such as ['createdAt', 'updatedAt'].",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/cms/v3/domains/_getPage'."]:
    """Retrieve all existing domains in the CMS.

    Fetches a list of all domains that have been created in the CMS, with optional filters by creation or update date."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/domains/",
        method="GET",
        params=remove_none_values({
            "createdAt": created_at_date_filter,
            "createdAfter": get_domains_created_after_date,
            "createdBefore": created_before_date,
            "updatedAt": filter_by_update_date,
            "updatedAfter": filter_domains_updated_after,
            "updatedBefore": filter_updated_before_date,
            "sort": sort_criteria,
            "after": paging_cursor_token,
            "limit": maximum_results_per_page,
            "archived": return_archived,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(
    requires_auth=OAuth2(
        id="arcade-hubspot",
        scopes=[
            "cms.knowledge_base.settings.read",
            "cms.domains.read",
            "cms.knowledge_base.settings.write",
            "content",
            "cms.domains.write",
        ],
    )
)
async def get_domain_by_id(
    context: ToolContext,
    domain_id: Annotated[
        str, "The unique ID of the domain to retrieve details for from HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/domains/{domainId}_getById'."
]:
    """Retrieve domain details by ID from HubSpot CMS.

    This tool retrieves detailed information about a specific domain using the provided domain ID from the HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/domains/{domainId}".format(domainId=domain_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def export_hubdb_draft_table(
    context: ToolContext,
    table_id_or_name: Annotated[
        str,
        "The ID or name of the table to export. Use this to specify the exact table you want to download.",  # noqa: E501
    ],
    export_file_format: Annotated[
        str | None,
        "The file format for exporting the draft table. Choose from `CSV`, `XLSX`, or `XLS`.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/draft/export_exportDraftTable'.",  # noqa: E501
]:
    """Export HubDB draft table to CSV or Excel format.

    Use this tool to export the draft version of a HubDB table in HubSpot CMS to a CSV or Excel file. Call this tool when you need to download table data for review or offline use."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft/export".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="GET",
        params=remove_none_values({"format": export_file_format}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_all_draft_tables_details(
    context: ToolContext,
    content_type: Annotated[
        str | None,
        "Specify the content type filter for retrieving draft tables. Leave empty for no filter.",
    ] = None,
    created_after_date: Annotated[
        str | None,
        "Return tables created after the specified date and time. Format: YYYY-MM-DDTHH:MM:SSZ.",
    ] = None,
    created_at_exact_time: Annotated[
        str | None, "Return tables created at the specified exact time (ISO 8601 format)."
    ] = None,
    created_before_date: Annotated[
        str | None, "Return tables created before this specified time."
    ] = None,
    filter_by_updated_before_date: Annotated[
        str | None, "Return tables last updated before this specified time in ISO 8601 format."
    ] = None,
    include_archived_tables: Annotated[
        bool | None, "Set to true to include archived tables in the results, false by default."
    ] = None,
    include_localized_schema: Annotated[
        bool | None,
        "Indicates whether to include localized schema information for draft tables. Accepts a boolean value.",  # noqa: E501
    ] = None,
    maximum_results_limit: Annotated[
        int | None,
        "Specifies the maximum number of draft table results to return, with a default of 1000.",
    ] = None,
    pagination_cursor_token: Annotated[
        str | None, "Cursor token to fetch the next set of results from a paginated response."
    ] = None,
    return_tables_updated_after: Annotated[
        str | None,
        "Only return tables that were last updated after the specified timestamp (ISO 8601 format).",  # noqa: E501
    ] = None,
    sort_fields_for_results: Annotated[
        list[str] | None,
        "Fields to sort the results by. Valid fields are `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Defaults to `createdAt`.",  # noqa: E501
    ] = None,
    updated_at_specific_time: Annotated[
        str | None, "Return tables last updated at the specified exact time."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/draft_getAllDraftTables'.",
]:
    """Retrieve details of all draft HubDB tables.

    Returns information on all draft tables in a specified HubSpot account, including column definitions."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/draft",
        method="GET",
        params=remove_none_values({
            "sort": sort_fields_for_results,
            "after": pagination_cursor_token,
            "limit": maximum_results_limit,
            "createdAt": created_at_exact_time,
            "createdAfter": created_after_date,
            "createdBefore": created_before_date,
            "updatedAt": updated_at_specific_time,
            "updatedAfter": return_tables_updated_after,
            "updatedBefore": filter_by_updated_before_date,
            "contentType": content_type,
            "archived": include_archived_tables,
            "isGetLocalizedSchema": include_localized_schema,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_table_row(
    context: ToolContext,
    row_id: Annotated[str, "The ID of the row to retrieve from the table."],
    table_id_or_name: Annotated[
        str, "The ID or name of the HubSpot CMS table to retrieve the row from."
    ],
    archived: Annotated[
        bool | None,
        "A boolean to retrieve archived rows. Set to True to include archived rows, or False to exclude them.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}_getTableRow'.",  # noqa: E501
]:
    """Get a single row from a HubSpot CMS table by ID.

    Use this tool to retrieve a specific row from the published version of a HubSpot CMS table by providing the table ID or name and the row ID. This can be done without authentication if the table allows public access."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}".format(  # noqa: UP032
            tableIdOrName=table_id_or_name, rowId=row_id
        ),
        method="GET",
        params=remove_none_values({"archived": archived}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_draft_hubdb_table_rows(
    context: ToolContext,
    table_id_or_name: Annotated[str, "The ID or name of the HubDB table to query for draft rows."],
    include_archived_rows: Annotated[
        bool | None,
        "Specify whether to include archived rows in the results. Use `true` to include archived rows, `false` to exclude them.",  # noqa: E501
    ] = None,
    maximum_results_limit: Annotated[
        int | None,
        "Specifies the maximum number of results to return from the draft table. Default is 1000.",
    ] = None,
    next_page_cursor: Annotated[
        str | None,
        "Cursor token to retrieve the next set of results, from `paging.next.after` in a paged response.",  # noqa: E501
    ] = None,
    result_offset: Annotated[
        int | None,
        "The number of rows to skip before starting to collect the result set from the draft version of a table.",  # noqa: E501
    ] = None,
    sort_columns: Annotated[
        list[str] | None,
        "List of column names to sort the results by. Use format like ['column1', '-column2'] to specify ascending or descending order.",  # noqa: E501
    ] = None,
    specified_columns: Annotated[
        list[str] | None,
        "Specify the column names to return only these columns in the result, instead of all columns.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft_readDraftTableRows'.",  # noqa: E501
]:
    """Retrieve rows from a draft HubDB table with optional filtering.

    This tool fetches rows from the draft version of a specified HubDB table. It allows for filtering and sorting rows based on query parameters, helping to access only relevant data."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="GET",
        params=remove_none_values({
            "sort": sort_columns,
            "after": next_page_cursor,
            "limit": maximum_results_limit,
            "properties": specified_columns,
            "offset": result_offset,
            "archived": include_archived_rows,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def clone_hubdb_draft_row(
    context: ToolContext,
    row_id_to_clone: Annotated[str, "The ID of the row to be cloned in the draft table."],
    table_id_or_name: Annotated[str, "The ID or name of the table to be cloned."],
    new_row_name: Annotated[
        str | None, "The name for the cloned row. Specify a new name if required."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft/clone_cloneDraftTableRow'.",  # noqa: E501
]:
    """Clone a single row in a HubDB draft table.

    Use this tool to clone a specific row in the draft version of a HubDB table within HubSpot CMS. This is helpful for duplicating draft content before finalizing changes."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft/clone".format(  # noqa: UP032
            tableIdOrName=table_id_or_name, rowId=row_id_to_clone
        ),
        method="POST",
        params=remove_none_values({"name": new_row_name}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def reset_hubdb_draft_to_published(
    context: ToolContext,
    table_identifier: Annotated[
        str, "The ID or name of the HubDB table to reset to the published version."
    ],
    include_foreign_ids: Annotated[
        bool | None, "Set to true to populate foreign ID values in the response."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/draft/reset_resetDraftTable'.",  # noqa: E501
]:
    """Reset HubDB draft table to match the published version.

    Replaces the draft version of a HubDB table with its published version, discarding any unpublished changes."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft/reset".format(  # noqa: UP032
            tableIdOrName=table_identifier
        ),
        method="POST",
        params=remove_none_values({"includeForeignIds": include_foreign_ids}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def export_table_from_hubdb(
    context: ToolContext,
    hubdb_table_id_or_name: Annotated[str, "The ID or name of the HubDB table to export."],
    file_format_to_export: Annotated[
        str | None, "The file format for exporting the table. Options are `CSV`, `XLSX`, and `XLS`."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/export_exportTable'.",
]:
    """Exports a HubDB table in the desired format.

    Use this tool to export the published version of a HubDB table by specifying the table ID or name. It allows exporting in various formats."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/export".format(  # noqa: UP032
            tableIdOrName=hubdb_table_id_or_name
        ),
        method="GET",
        params=remove_none_values({"format": file_format_to_export}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def replace_draft_table_rows(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_identifier: Annotated[
        str | None,
        "The ID or name of the table in which you want to replace draft rows.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/replace_replaceDraftTableRows'.",  # noqa: E501
]:
    """Batch replace rows in HubSpot CMS draft tables.

    This tool replaces multiple rows in the draft version of a HubSpot CMS table, allowing up to 100 rows per call. Useful for updating entries en masse in preparation for publication.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROWS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_identifier:
        missing_params.append(("table_identifier", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROWS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROWS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/replace".format(  # noqa: UP032
            tableIdOrName=table_identifier
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROWS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def clone_hubdb_table(
    context: ToolContext,
    copy_rows: Annotated[
        bool, "Boolean indicating whether to copy the rows during the cloning process."
    ],
    is_hubspot_defined: Annotated[
        bool,
        "Indicate if the table is defined by HubSpot. This is a boolean value where true means the table is HubSpot-defined.",  # noqa: E501
    ],
    source_table_id_or_name: Annotated[str, "The ID or name of the HubDB table to clone."],
    new_table_label: Annotated[
        str | None,
        "The label for the new cloned table. Specify a descriptive label for identification.",
    ] = None,
    new_table_name: Annotated[
        str | None, "The desired new name for the cloned HubDB table draft."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/draft/clone_cloneDraftTable'.",  # noqa: E501
]:
    """Clone an existing HubDB table as a draft.

    Use this tool to create a draft clone of an existing HubDB table. The new table's name and label can be specified."""  # noqa: E501
    request_data: Any = {
        "newName": new_table_name,
        "isHubspotDefined": is_hubspot_defined,
        "newLabel": new_table_label,
        "copyRows": copy_rows,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft/clone".format(  # noqa: UP032
            tableIdOrName=source_table_id_or_name
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def permanently_delete_hubdb_draft_rows(
    context: ToolContext,
    row_ids_to_delete: Annotated[
        list[str],
        "An array of up to 100 row IDs to permanently delete from the draft version of the table.",
    ],
    table_id_or_name: Annotated[
        str, "The ID or name of the HubDB table to target for deleting draft rows."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/purge_purgeDraftTableRows'.",  # noqa: E501
]:
    """Permanently delete draft rows from a HubDB table.

    Use this tool to permanently delete rows from the draft version of a HubDB table. A maximum of 100 row IDs can be deleted per call."""  # noqa: E501
    request_data: Any = {"inputs": row_ids_to_delete}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/purge".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_draft_table_row_by_id(
    context: ToolContext,
    row_id: Annotated[str, "The ID of the row in the table's draft version to retrieve."],
    table_identifier: Annotated[
        str, "The ID or name of the HubDB table to fetch the draft row from."
    ],
    include_archived: Annotated[
        bool | None, "Set to true to include archived rows in the search result. Defaults to false."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft_getDraftTableRowById'.",  # noqa: E501
]:
    """Retrieve a single draft row by ID from a HubDB table.

    This tool fetches a specific row's draft version from a HubDB table using the row ID. It should be called when a user needs to access draft data for a specific table row by ID or name."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft".format(  # noqa: UP032
            tableIdOrName=table_identifier, rowId=row_id
        ),
        method="GET",
        params=remove_none_values({"archived": include_archived}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def replace_draft_table_row(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_id_or_name: Annotated[
        str | None,
        "The ID or name of the HubDB table to replace the row in.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    row_id: Annotated[
        str | None,
        "The unique identifier of the row to be replaced in the draft table.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft_replaceDraftTableRow'.",  # noqa: E501
]:
    """Replace a row in the draft version of a HubDB table.

    This tool replaces a single row in the draft version of a specified HubDB table. All column values must be provided. Omitted columns will be deleted from the row.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROW"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_id_or_name:
        missing_params.append(("table_id_or_name", "path"))
    if not row_id:
        missing_params.append(("row_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROW"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROW"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft".format(  # noqa: UP032
            tableIdOrName=table_id_or_name, rowId=row_id
        ),
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REPLACEDRAFTTABLEROW"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def delete_draft_table_row_hubspot(
    context: ToolContext,
    row_id: Annotated[
        str,
        "The unique ID of the row to be permanently deleted from the draft version of the HubDB table.",  # noqa: E501
    ],
    table_id_or_name: Annotated[
        str, "The ID or name of the HubDB table from which the draft row will be deleted."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft_purgeDraftTableRow'.",  # noqa: E501
]:
    """Permanently delete a row from a HubDB draft table.

    Use this tool to permanently delete a specified row from the draft version of a HubDB table in HubSpot CMS. Call this tool when you need to remove a row that is no longer needed in the draft."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft".format(  # noqa: UP032
            tableIdOrName=table_id_or_name, rowId=row_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def update_hubdb_row_draft(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_identifier: Annotated[
        str | None,
        "The ID or name of the HubDB table to update the draft row in.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    row_id: Annotated[
        str | None,
        "The unique ID of the row to be updated in the draft table.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft_updateDraftTableRow'.",  # noqa: E501
]:
    """Update specific fields in a HubDB table's draft row.

    This tool updates specific fields in a HubDB table's draft row. It should be called when you need to modify only certain columns of a row in the draft version of a HubDB table. You don't have to specify all fields, only those you intend to update.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEHUBDBROWDRAFT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_identifier:
        missing_params.append(("table_identifier", "path"))
    if not row_id:
        missing_params.append(("row_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBDBROWDRAFT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBDBROWDRAFT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft".format(  # noqa: UP032
            tableIdOrName=table_identifier, rowId=row_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBDBROWDRAFT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def fetch_hubdb_table_rows(
    context: ToolContext,
    table_identifier: Annotated[
        str, "The ID or name of the HubDB table to query for fetching row data."
    ],
    include_archived_rows: Annotated[
        bool | None, "Set to true to include archived rows in the results; false to exclude them."
    ] = None,
    maximum_results_limit: Annotated[
        int | None,
        "Specifies the maximum number of results to return. The default value is 1000, which can be adjusted to retrieve fewer entries.",  # noqa: E501
    ] = None,
    pagination_cursor_token: Annotated[
        str | None,
        "Cursor token to fetch the next set of results. Obtainable from the `paging.next.after` of a paged response.",  # noqa: E501
    ] = None,
    requested_columns: Annotated[
        list[str] | None,
        "Specify column names to retrieve only the required columns' data, excluding others.",
    ] = None,
    row_offset: Annotated[
        int | None,
        "The starting point for fetching a subset of rows, useful for pagination. It's similar to specifying which row to start fetching from.",  # noqa: E501
    ] = None,
    sort_columns: Annotated[
        list[str] | None,
        "List of column names to sort the results by. Each entry is a string representing a column. Prefix with '-' for descending order.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/rows_getTableRows'.",
]:
    """Fetch rows from a HubDB table using filters and sorting.

    Use this tool to fetch rows from a specific HubDB table. You can apply filters and sorting by using query parameters, allowing retrieval of customized data sets. No authentication is needed if the table allows public access."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows".format(  # noqa: UP032
            tableIdOrName=table_identifier
        ),
        method="GET",
        params=remove_none_values({
            "sort": sort_columns,
            "after": pagination_cursor_token,
            "limit": maximum_results_limit,
            "properties": requested_columns,
            "offset": row_offset,
            "archived": include_archived_rows,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def add_hubdb_table_row(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    target_table_id_or_name: Annotated[
        str | None,
        "The ID or name of the target HubDB table to add the row to.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows_createTableRow'.",  # noqa: E501
]:
    """Add a new row to a HubDB draft table.

    Use this tool to add a new row to the draft version of a HubDB table. To make these changes live, publish the table afterwards.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ADDHUBDBTABLEROW"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not target_table_id_or_name:
        missing_params.append(("target_table_id_or_name", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["ADDHUBDBTABLEROW"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["ADDHUBDBTABLEROW"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows".format(  # noqa: UP032
            tableIdOrName=target_table_id_or_name
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ADDHUBDBTABLEROW"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def read_hubdb_table_rows(
    context: ToolContext,
    hubdb_table_id_or_name: Annotated[
        str,
        "The ID or name of the HubDB table to query. Use this to specify which table's rows to retrieve.",  # noqa: E501
    ],
    row_ids: Annotated[
        list[str],
        "An array of strings representing the row IDs to retrieve from the specified HubDB table.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/batch/read_readTableRows'.",  # noqa: E501
]:
    """Retrieve rows from a published HubDB table.

    This tool fetches rows from a specified HubDB table using row IDs. It can be used when the table allows public access, so no authentication is needed."""  # noqa: E501
    request_data: Any = {"inputs": row_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/batch/read".format(  # noqa: UP032
            tableIdOrName=hubdb_table_id_or_name
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def create_draft_table_rows(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_id_or_name: Annotated[
        str | None,
        "The ID or name of the HubSpot table to which the draft rows are being added. This is required to specify which table you want to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/create_createDraftTableRows'.",  # noqa: E501
]:
    """Create draft rows in a specified HubSpot table.

    This tool is used to add multiple rows to the draft version of a specified HubSpot CMS table. It allows for up to 100 rows to be created in one call. Useful for updating content drafts before publication.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEDRAFTTABLEROWS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_id_or_name:
        missing_params.append(("table_id_or_name", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEDRAFTTABLEROWS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEDRAFTTABLEROWS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/create".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEDRAFTTABLEROWS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_hubdb_table_details(
    context: ToolContext,
    table_id_or_name: Annotated[str, "The ID or name of the HubDB table to retrieve details for."],
    get_localized_schema: Annotated[
        bool | None,
        "Set to `true` to return the localized schema for the table. If `false`, returns the default schema.",  # noqa: E501
    ] = None,
    include_foreign_id_values: Annotated[
        bool | None, "Set this to true to populate foreign ID values in the result."
    ] = None,
    return_archived_table_details: Annotated[
        bool | None, "Set to true to return details for an archived table. Defaults to false."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}_getTableDetails'.",
]:
    """Get details of a HubDB table, including columns and row count.

    Use this tool to obtain comprehensive details about a specific HubDB table. It returns column definitions and the number of rows for the published version of the table. Accessible without authentication if the table is publicly available; requires HubSpot account ID in `portalId` query parameter."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="GET",
        params=remove_none_values({
            "archived": return_archived_table_details,
            "includeForeignIds": include_foreign_id_values,
            "isGetLocalizedSchema": get_localized_schema,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def archive_hubdb_table(
    context: ToolContext,
    hubdb_table_identifier: Annotated[
        str,
        "The ID or name of the HubDB table to archive. Provides the reference needed to identify the table within the system.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/hubdb/tables/{tableIdOrName}_archiveTable'.",
]:
    """Archive a HubDB table in HubSpot CMS.

    Use this tool to archive (soft delete) an existing HubDB table in HubSpot CMS, affecting both published and draft versions."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}".format(  # noqa: UP032
            tableIdOrName=hubdb_table_identifier
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_hubdb_draft_rows(
    context: ToolContext,
    row_ids: Annotated[
        list[str], "An array of row IDs to retrieve draft rows from the specified HubDB table."
    ],
    table_id_or_name: Annotated[str, "ID or name of the HubDB table to retrieve draft rows from."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/read_readDraftTableRows'.",  # noqa: E501
]:
    """Fetch draft rows from a specified HubDB table.

    Use this tool to retrieve rows from the draft version of a specified HubDB table using a set of row IDs."""  # noqa: E501
    request_data: Any = {"inputs": row_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/read".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def clone_draft_table_rows_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubdb_table_id_or_name: Annotated[
        str | None,
        "The ID or name of the HubDB table to clone rows from. Specify either the unique table ID or its name.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/clone_cloneDraftTableRows'.",  # noqa: E501
]:
    """Clone rows in a draft HubDB table by row IDs.

    Use this tool to clone specific rows in the draft version of a HubDB table by providing up to 100 row IDs. Useful for duplicating data entries during content management in HubSpot's CMS.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CLONEDRAFTTABLEROWSHUBSPOT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not hubdb_table_id_or_name:
        missing_params.append(("hubdb_table_id_or_name", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CLONEDRAFTTABLEROWSHUBSPOT"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["CLONEDRAFTTABLEROWSHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/clone".format(  # noqa: UP032
            tableIdOrName=hubdb_table_id_or_name
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CLONEDRAFTTABLEROWSHUBSPOT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_all_hubdb_tables(
    context: ToolContext,
    content_type_filter: Annotated[
        str | None, "Filter tables by the specified content type to return only those matching it."
    ] = None,
    created_after: Annotated[
        str | None, "Return tables created after the specified time in ISO 8601 format."
    ] = None,
    created_before_timestamp: Annotated[
        str | None, "Return tables created before this timestamp. Format should be in ISO 8601."
    ] = None,
    cursor_token_for_next_results: Annotated[
        str | None,
        "The cursor token to retrieve the next set of results. Obtain this from the `paging.next.after` in a paged response.",  # noqa: E501
    ] = None,
    filter_by_creation_time: Annotated[
        str | None, "Return tables created at the specified time. Format: ISO 8601."
    ] = None,
    include_archived_tables: Annotated[
        bool | None,
        "Specifies whether to include archived tables in the results. Defaults to `false`.",
    ] = None,
    include_localized_schema: Annotated[
        bool | None, "Include localized schema details in the response if true."
    ] = None,
    last_updated_exact_time: Annotated[
        str | None,
        "Only return tables last updated at exactly the specified time, formatted as a string.",
    ] = None,
    max_table_results: Annotated[
        int | None, "Maximum number of HubDB table results to return. Default is 1000."
    ] = None,
    sort_fields: Annotated[
        list[str] | None,
        "Specify fields for sorting results: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Defaults to `createdAt`.",  # noqa: E501
    ] = None,
    updated_after_timestamp: Annotated[
        str | None,
        "Return tables last updated after this timestamp. Format: 'YYYY-MM-DDTHH:MM:SSZ'.",
    ] = None,
    updated_before_time: Annotated[
        str | None, "Return tables updated before this specific time."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/hubdb/tables_getAllTables'."
]:
    """Retrieve details of all published HubDB tables.

    This tool returns the details and column definitions for all published HubDB tables in a HubSpot account. It is useful when you need an overview of the schema and data of all tables within your CMS environment."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables",
        method="GET",
        params=remove_none_values({
            "sort": sort_fields,
            "after": cursor_token_for_next_results,
            "limit": max_table_results,
            "createdAt": filter_by_creation_time,
            "createdAfter": created_after,
            "createdBefore": created_before_timestamp,
            "updatedAt": last_updated_exact_time,
            "updatedAfter": updated_after_timestamp,
            "updatedBefore": updated_before_time,
            "contentType": content_type_filter,
            "archived": include_archived_tables,
            "isGetLocalizedSchema": include_localized_schema,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def create_hubdb_table(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/hubdb/tables_createTable'."
]:
    """Create a new draft HubDB table with a unique name and label.

    This tool creates a new draft HubDB table using a provided JSON schema. It requires that the table name and label be unique within each account.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEHUBDBTABLE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEHUBDBTABLE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEHUBDBTABLE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBDBTABLE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def delete_table_version(
    context: ToolContext,
    table_identifier: Annotated[
        str, "The unique ID or name of the HubDB table whose version you want to delete."
    ],
    table_version_id: Annotated[
        int,
        "Specify the ID of the table version to delete. This should be an integer value that identifies a specific version in HubDB.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/hubdb/tables/{tableIdOrName}/versions/{versionId}_removeTableVersion'.",  # noqa: E501
]:
    """Delete a specific version of a HubDB table.

    This tool deletes a specified version of a table in HubSpot CMS HubDB. It should be called when you need to remove an obsolete or incorrect version of a table. Make sure to specify the correct table and version identifiers."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/versions/{versionId}".format(  # noqa: UP032
            tableIdOrName=table_identifier, versionId=table_version_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def unpublish_hubdb_table(
    context: ToolContext,
    table_identifier: Annotated[str, "The ID or name of the HubDB table to unpublish."],
    include_foreign_ids: Annotated[
        bool | None, "Set this to true to populate foreign ID values in the response."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/unpublish_unpublishTable'.",  # noqa: E501
]:
    """Unpublish a HubDB table to stop rendering data on website pages.

    Use this tool to unpublish a HubDB table. This action will prevent any website pages from rendering data associated with the table."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/unpublish".format(  # noqa: UP032
            tableIdOrName=table_identifier
        ),
        method="POST",
        params=remove_none_values({"includeForeignIds": include_foreign_ids}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def get_draft_table_details(
    context: ToolContext,
    table_id_or_name: Annotated[
        str, "The ID or name of the HubDB table to retrieve draft details for."
    ],
    include_foreign_ids: Annotated[
        bool | None, "Set this to `true` to populate foreign ID values in the result."
    ] = None,
    include_localized_schema: Annotated[
        bool | None, "Set to true to include the localized schema in the result."
    ] = None,
    return_archived_table: Annotated[
        bool | None, "Set to `true` to return an archived table. Defaults to `false`."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/hubdb/tables/{tableIdOrName}/draft_getDraftTableDetailsById'.",  # noqa: E501
]:
    """Retrieve details of a draft HubDB table by ID or name.

    Use this tool to get information about the draft version of a specific HubDB table, including column definitions and row count."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="GET",
        params=remove_none_values({
            "archived": return_archived_table,
            "includeForeignIds": include_foreign_ids,
            "isGetLocalizedSchema": include_localized_schema,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def update_hubdb_draft_table(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_id_or_name: Annotated[
        str | None,
        "The ID or name of the HubDB table to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    return_archived_tables: Annotated[
        bool | None,
        "Set to true to return archived tables. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    populate_foreign_ids: Annotated[
        bool | None,
        "Set to true to populate foreign ID values in the table result.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    retrieve_localized_schema: Annotated[
        bool | None,
        "Set to true to retrieve the localized schema of the HubDB table, if available.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/hubdb/tables/{tableIdOrName}/draft_updateDraftTable'.",  # noqa: E501
]:
    """Update or modify a HubDB table draft in HubSpot CMS.

    Use this tool to update an existing HubDB table by adding or removing columns, or restoring an archived table. Changes apply only to the draft version until published. Ensure all columns are included in the request to avoid deletion.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEHUBDBDRAFTTABLE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_id_or_name:
        missing_params.append(("table_id_or_name", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBDBDRAFTTABLE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEHUBDBDRAFTTABLE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBDBDRAFTTABLE"],
        params=remove_none_values({
            "archived": return_archived_tables,
            "includeForeignIds": populate_foreign_ids,
            "isGetLocalizedSchema": retrieve_localized_schema,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def publish_table_draft(
    context: ToolContext,
    table_identifier: Annotated[
        str,
        "The ID or name of the HubSpot CMS table to publish. This identifies which draft table's data and schema should be copied to the published version.",  # noqa: E501
    ],
    include_foreign_id_values: Annotated[
        bool | None, "Set to `true` to populate foreign ID values in the response."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/draft/publish_publishDraftTable'.",  # noqa: E501
]:
    """Publish draft table to update website pages.

    This tool publishes a HubSpot CMS table by copying data and schema changes from the draft to the published version. Use it to ensure that any website pages using the table are updated with the latest draft content."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/draft/publish".format(  # noqa: UP032
            tableIdOrName=table_identifier
        ),
        method="POST",
        params=remove_none_values({"includeForeignIds": include_foreign_id_values}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["hubdb"]))
async def batch_update_table_rows_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    table_id_or_name: Annotated[
        str | None,
        "The ID or name of the table to update rows in the draft version.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/update_updateDraftTableRows'.",  # noqa: E501
]:
    """Update multiple draft table rows in HubSpot CMS.

    This tool updates up to 100 rows in the draft version of a specified HubSpot CMS table. Use it when you need to apply changes to several rows at once in a draft table.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["BATCHUPDATETABLEROWSHUBSPOT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not table_id_or_name:
        missing_params.append(("table_id_or_name", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["BATCHUPDATETABLEROWSHUBSPOT"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["BATCHUPDATETABLEROWSHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/hubdb/tables/{tableIdOrName}/rows/draft/batch/update".format(  # noqa: UP032
            tableIdOrName=table_id_or_name
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHUPDATETABLEROWSHUBSPOT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["media_bridge.write"]))
async def create_attention_span_event(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/media-bridge/v1/events/attention-span'."
]:
    """Log viewer's attention span details for media events.

    This tool creates an event recording viewer attention span details for media, which helps in tracking engagement and interaction levels.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEATTENTIONSPANEVENT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEATTENTIONSPANEVENT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEATTENTIONSPANEVENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/media-bridge/v1/events/attention-span",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEATTENTIONSPANEVENT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["media_bridge.write"]))
async def track_media_milestones(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/media-bridge/v1/events/media-played-percent'.",
]:
    """Log user progress milestones in media content viewing.

    This tool creates an event to track when a user reaches quarterly viewing milestones in a piece of media. It should be called whenever it's necessary to log this progress for analytics or tracking purposes.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["TRACKMEDIAMILESTONES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["TRACKMEDIAMILESTONES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["TRACKMEDIAMILESTONES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/media-bridge/v1/events/media-played-percent",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["TRACKMEDIAMILESTONES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["media_bridge.write"]))
async def create_media_played_event(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/media-bridge/v1/events/media-played'."
]:
    """Log an event when media playback starts.

    Call this tool when you need to create an event for when a user begins playing a piece of media. It helps track media interactions.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEMEDIAPLAYEDEVENT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMEDIAPLAYEDEVENT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEMEDIAPLAYEDEVENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/media-bridge/v1/events/media-played",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMEDIAPLAYEDEVENT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_previous_site_page_version(
    context: ToolContext,
    site_page_id: Annotated[
        str, "The unique identifier for the site page whose previous version is to be retrieved."
    ],
    site_page_revision_id: Annotated[
        str, "The ID of the specific revision of the site page to retrieve."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}_getPreviousVersion'.",  # noqa: E501
]:
    """Retrieve a previous version of a site page.

    Use this tool to access details of a previous revision of a site page by specifying the object and revision IDs."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}".format(  # noqa: UP032
            objectId=site_page_id, revisionId=site_page_revision_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_page_previous_versions(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The ID of the Landing Page to retrieve previous versions for."
    ],
    cursor_token_for_next_set: Annotated[
        str | None,
        "The cursor token for retrieving the next set of results, from the `paging.next.after` JSON property.",  # noqa: E501
    ] = None,
    limit_page_fetching_before_cursor: Annotated[
        str | None,
        "A token used for fetching results before a specific cursor position in paged results. Typically used in pagination to navigate backwards.",  # noqa: E501
    ] = None,
    max_results: Annotated[
        int | None,
        "The maximum number of previous landing page versions to return. Default is 100.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/{objectId}/revisions_getPreviousVersions'.",  # noqa: E501
]:
    """Retrieve previous versions of a Landing Page for review.

    Use this tool to obtain all previous versions of a specified Landing Page, allowing you to track changes or restore versions if needed."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/revisions".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="GET",
        params=remove_none_values({
            "after": cursor_token_for_next_set,
            "before": limit_page_fetching_before_cursor,
            "limit": max_results,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_site_pages_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    update_deleted_site_pages: Annotated[
        bool | None,
        "Specify true to update deleted Site Pages. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/batch/update_updateBatch'.",
]:
    """Batch update specified site pages in HubSpot CMS.

    Use this tool to update multiple site pages in HubSpot CMS based on identifiers provided in the request body. It should be called when you need to make bulk updates to site page objects.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATESITEPAGESBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGESBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESITEPAGESBATCH"],
        params=remove_none_values({"archived": update_deleted_site_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_previous_folder_versions(
    context: ToolContext,
    folder_id: Annotated[str, "The unique ID of the folder to retrieve its previous versions."],
    before_token: Annotated[
        str | None,
        "A cursor token to fetch items before a certain point in the result set, used for pagination.",  # noqa: E501
    ] = None,
    max_results: Annotated[
        int | None, "Specifies the maximum number of folder versions to retrieve. Default is 100."
    ] = None,
    pagination_cursor_after: Annotated[
        str | None,
        "The cursor token to retrieve the next set of results from the `paging.next.after` property in a paged response.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/folders/{objectId}/revisions_getFolderPreviousVersions'.",  # noqa: E501
]:
    """Retrieve previous versions of a CMS folder.

    This tool retrieves all previous versions of a specified folder from the CMS. It should be called when there's a need to access or analyze the revision history of a folder."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}/revisions".format(  # noqa: UP032
            objectId=folder_id
        ),
        method="GET",
        params=remove_none_values({
            "after": pagination_cursor_after,
            "before": before_token,
            "limit": max_results,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_page_version(
    context: ToolContext,
    page_version_to_restore: Annotated[
        str, "The ID of the site page version to restore. Use this to revert to a previous version."
    ],
    site_page_id: Annotated[str, "The unique identifier for the Site Page to be restored."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}/restore_restorePreviousVersion'.",  # noqa: E501
]:
    """Restore a specific version of a HubSpot site page.

    Use this tool to restore a specified version of a HubSpot site page. Ideal for undoing changes or reverting to a previous design or content state."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}/restore".format(  # noqa: UP032
            objectId=site_page_id, revisionId=page_version_to_restore
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_page_folders(
    context: ToolContext,
    created_after: Annotated[
        str | None, "Return folders created after this specified ISO 8601 timestamp."
    ] = None,
    created_before_time: Annotated[
        str | None, "Return folders created before this specific time."
    ] = None,
    exact_update_time: Annotated[
        str | None,
        "Return folders last updated at exactly the specified time. Use a string format for the timestamp.",  # noqa: E501
    ] = None,
    filter_folders_created_at: Annotated[
        str | None, "Return folders created at an exact specified time. Use ISO 8601 format."
    ] = None,
    folder_property: Annotated[
        str | None,
        "Specify properties to include in the response for each folder. Leave empty for all properties.",  # noqa: E501
    ] = None,
    include_deleted_folders: Annotated[
        bool | None, "Include deleted folders in the results when set to true. Defaults to false."
    ] = None,
    max_results_limit: Annotated[
        int | None, "The maximum number of folder results to return. Default is 100."
    ] = None,
    next_page_cursor: Annotated[
        str | None, "The cursor token to retrieve the next set of results from a paged response."
    ] = None,
    sort_criteria: Annotated[
        list[str] | None,
        "Fields to use for sorting results. Valid options: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Default: `createdAt`.",  # noqa: E501
    ] = None,
    updated_after_date: Annotated[
        str | None,
        "Only return folders last updated after the specified time. Use ISO 8601 format for the date and time.",  # noqa: E501
    ] = None,
    updated_before_timestamp: Annotated[
        str | None, "Return folders last updated before this specified timestamp."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/folders_getFoldersPage'.",
]:
    """Retrieve the list of landing page folders from HubSpot CMS.

    This tool fetches the list of landing page folders in HubSpot CMS, supporting paging and filtering. Useful for integrations needing to analyze these models or suggest edits via external services."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders",
        method="GET",
        params=remove_none_values({
            "createdAt": filter_folders_created_at,
            "createdAfter": created_after,
            "createdBefore": created_before_time,
            "updatedAt": exact_update_time,
            "updatedAfter": updated_after_date,
            "updatedBefore": updated_before_timestamp,
            "sort": sort_criteria,
            "after": next_page_cursor,
            "limit": max_results_limit,
            "archived": include_deleted_folders,
            "property": folder_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_landing_page_folder(
    context: ToolContext,
    deletion_timestamp: Annotated[
        str,
        "The ISO8601 timestamp indicating when the folder was deleted. Leave this empty if the folder is active.",  # noqa: E501
    ],
    folder_category: Annotated[
        int, "The type of object this folder applies to. Must be set to LANDING_PAGE."
    ],
    folder_creation_date: Annotated[str, "The ISO8601 timestamp when this folder was created."],
    folder_name: Annotated[str, "The name of the folder to display in the app dashboard."],
    folder_unique_id: Annotated[
        str,
        "The unique identifier for the content folder. This is a string that serves as the primary key for the folder.",  # noqa: E501
    ],
    parent_folder_id: Annotated[
        int, "The ID of the parent content folder under which the new folder will be nested."
    ],
    updated_timestamp: Annotated[
        str, "The timestamp in ISO8601 format indicating when the folder was last updated."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders_createFolder'.",
]:
    """Create a new folder in HubSpot CMS for landing pages.

    This tool is used to create a new folder within the landing pages section of HubSpot CMS. Call this tool when you need to organize landing pages into a new folder structure."""  # noqa: E501
    request_data: Any = {
        "deletedAt": deletion_timestamp,
        "parentFolderId": parent_folder_id,
        "created": folder_creation_date,
        "name": folder_name,
        "id": folder_unique_id,
        "category": folder_category,
        "updated": updated_timestamp,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_page_to_language_group(
    context: ToolContext,
    object_id_to_add_to_language_group: Annotated[
        str, "ID of the site page object to add to a multi-language group."
    ],
    page_language: Annotated[
        str,
        "Designated language code (e.g., 'en', 'fr') of the page to add to a multi-language group.",
    ],
    primary_language_object_id: Annotated[
        str,
        "ID of the primary language object in the multi-language group to which the page will be attached.",  # noqa: E501
    ],
    primary_language_of_group: Annotated[
        str | None, "Specify the primary language for the multi-language group."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/multi-language/attach-to-lang-group_attachToLangGroup'.",  # noqa: E501
]:
    """Attach a site page to a multi-language group.

    Use this tool to attach a specific site page to an existing multi-language group, enabling multi-language support for that page."""  # noqa: E501
    request_data: Any = {
        "language": page_language,
        "id": object_id_to_add_to_language_group,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language_of_group,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def schedule_site_page_publication(
    context: ToolContext,
    object_id_to_schedule: Annotated[
        str, "The ID of the site page to be scheduled for publication."
    ],
    publication_date: Annotated[
        str,
        "The date and time when the site page should be published. Use ISO 8601 format (e.g., '2023-12-31T23:59:00Z').",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/schedule_schedule'.",
]:
    """Schedule a site page for publication at a specified time.

    Use this tool to set a future publication date for a site page within the HubSpot CMS. It can be used to automate the release time of new content on your website."""  # noqa: E501
    request_data: Any = {"publishDate": publication_date, "id": object_id_to_schedule}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/schedule",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_site_page_from_language_group(
    context: ToolContext,
    object_id_to_detach: Annotated[
        str, "The ID of the site page to detach from the multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/multi-language/detach-from-lang-group_detachFromLangGroup'.",  # noqa: E501
]:
    """Detach a site page from a multi-language group.

    Use this tool to detach a site page from its current multi-language group when language-specific customization is needed."""  # noqa: E501
    request_data: Any = {"id": object_id_to_detach}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_landing_page_folders(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders/batch/create_createFolders'.",  # noqa: E501
]:
    """Create multiple landing page folders in HubSpot CMS.

    Use this tool to create multiple folder objects for landing pages in HubSpot CMS as specified in the request details.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATELANDINGPAGEFOLDERS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGEFOLDERS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGEFOLDERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATELANDINGPAGEFOLDERS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_previous_site_page_versions(
    context: ToolContext,
    site_page_id: Annotated[
        str, "The unique identifier for the Site Page to retrieve its previous versions."
    ],
    max_results_limit: Annotated[
        int | None,
        "The maximum number of previous versions to retrieve. Defaults to 100 if not specified.",
    ] = None,
    next_results_cursor: Annotated[
        str | None,
        "The cursor token to fetch the next set of site page versions. Obtain this from `paging.next.after` in the response of a previous request.",  # noqa: E501
    ] = None,
    page_version_cursor_before: Annotated[
        str | None,
        "The cursor token to get the set of results before a specific point. Useful for backward paging through results.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/site-pages/{objectId}/revisions_getPreviousVersions'.",  # noqa: E501
]:
    """Retrieve previous versions of a site page.

    Use this tool to access all previous versions of a specific site page in HubSpot's CMS. It's helpful for tracking changes or restoring an older version."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/revisions".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="GET",
        params=remove_none_values({
            "after": next_results_cursor,
            "before": page_version_cursor_before,
            "limit": max_results_limit,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_page_draft(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The unique identifier for the Landing Page draft to be retrieved."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/{objectId}/draft_getDraftById'.",  # noqa: E501
]:
    """Retrieve the full draft version of a landing page.

    Use this tool to get the complete draft version of a landing page by providing its object ID. Ideal for viewing or editing drafts before publishing."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/draft".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_page_draft(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    landing_page_id: Annotated[
        str | None,
        "The ID of the landing page to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/pages/landing-pages/{objectId}/draft_updateDraft'.",  # noqa: E501
]:
    """Update draft of a specific landing page by ID.

    This tool updates the draft version of a landing page in HubSpot CMS. Use it when you need to make changes to an existing draft. Only the fields specified will be updated, based on the landing page ID provided.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEDRAFT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not landing_page_id:
        missing_params.append(("landing_page_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEDRAFT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEDRAFT"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/draft".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_pages(
    context: ToolContext,
    created_after: Annotated[
        str | None, "Return landing pages created after this specified timestamp (ISO 8601 format)."
    ] = None,
    created_at_timestamp: Annotated[
        str | None,
        "Return landing pages created at the specified exact timestamp. Format: ISO 8601.",
    ] = None,
    created_before_timestamp: Annotated[
        str | None, "Only return landing pages created before the specified ISO 8601 timestamp."
    ] = None,
    cursor_after_token: Annotated[
        str | None,
        "The token to get the next set of results. Obtainable from `paging.next.after` in a paged response.",  # noqa: E501
    ] = None,
    filter_by_property: Annotated[
        str | None, "Specify a property to filter the landing pages by."
    ] = None,
    include_archived_pages: Annotated[
        bool | None,
        "Specify whether to include deleted landing pages in the results. Defaults to false.",
    ] = None,
    last_updated_before: Annotated[
        str | None, "Return landing pages updated before this specified datetime (ISO format)."
    ] = None,
    last_updated_exact_time: Annotated[
        str | None, "Return Landing Pages last updated at exactly this time. Use ISO 8601 format."
    ] = None,
    max_results: Annotated[
        int | None, "Specify the maximum number of landing pages to return. Default is 100."
    ] = None,
    sort_fields_for_results: Annotated[
        list[str] | None,
        "Specifies fields for sorting results. Valid options: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Default is `createdAt`.",  # noqa: E501
    ] = None,
    updated_after_datetime: Annotated[
        str | None, "Return landing pages updated after this datetime in ISO 8601 format."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/pages/landing-pages_getPage'."
]:
    """Retrieve a list of HubSpot CMS landing pages.

    This tool retrieves a list of landing pages from HubSpot CMS, supporting paging and filtering. Ideal for integrations that analyze page models and suggest edits."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages",
        method="GET",
        params=remove_none_values({
            "createdAt": created_at_timestamp,
            "createdAfter": created_after,
            "createdBefore": created_before_timestamp,
            "updatedAt": last_updated_exact_time,
            "updatedAfter": updated_after_datetime,
            "updatedBefore": last_updated_before,
            "sort": sort_fields_for_results,
            "after": cursor_after_token,
            "limit": max_results,
            "archived": include_archived_pages,
            "property": filter_by_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_landing_page(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/pages/landing-pages_create'."
]:
    """Create a new landing page in HubSpot.

    Use this tool to create a new landing page on HubSpot. Suitable for automating marketing or content management tasks.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATELANDINGPAGE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGE"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def end_active_ab_test(
    context: ToolContext,
    test_id_to_end: Annotated[
        str, "The unique identifier of the A/B test that you want to conclude in the HubSpot CMS."
    ],
    winner_id: Annotated[
        str,
        "The ID of the object to designate as the winner of the A/B test. This should match the ID used in the CMS to identify the specific variant.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/ab-test/end_endActiveABTest'.",
]:
    """End an active A/B test and designate a winner.

    Use this tool to conclude an ongoing A/B test and specify the winning variant on the HubSpot CMS."""  # noqa: E501
    request_data: Any = {"winnerId": winner_id, "abTestId": test_id_to_end}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/ab-test/end",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_site_page_to_draft(
    context: ToolContext,
    site_page_id: Annotated[str, "The ID of the Site Page to be restored to draft."],
    site_page_version_id_to_restore: Annotated[
        int, "The ID of the Site Page version to restore as the new draft."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}/restore-to-draft_restorePreviousVersionToDraft'.",  # noqa: E501
]:
    """Restore a specific site page version to draft.

    Use this tool to take a specified version of a HubSpot CMS site page and set it as the new draft version. This is useful when you need to revert a page to a previously saved version."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/revisions/{revisionId}/restore-to-draft".format(  # noqa: UP032
            objectId=site_page_id, revisionId=site_page_version_id_to_restore
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_landing_pages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/batch/create_createBatch'.",
]:
    """Create multiple landing pages in HubSpot CMS.

    Use this tool to create a batch of new landing pages in HubSpot CMS by providing the necessary details in the request body.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATELANDINGPAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATELANDINGPAGES"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/batch/create",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_pages_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    update_deleted_landing_pages: Annotated[
        bool | None,
        "Set to true to update deleted (archived) Landing Pages. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/batch/update_updateBatch'.",
]:
    """Batch update landing pages in HubSpot CMS.

    Use this tool to update multiple landing page objects in HubSpot CMS by providing their identifiers and new data in the request body.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGESBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGESBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGESBATCH"],
        params=remove_none_values({"archived": update_deleted_landing_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_landing_pages(
    context: ToolContext,
    landing_page_ids: Annotated[
        list[str], "Array of strings, each representing a Landing Page ID to retrieve details for."
    ],
    return_archived_landing_pages: Annotated[
        bool | None, "Specify whether to return deleted Landing Pages. Defaults to `false`."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/batch/read_readBatch'.",
]:
    """Retrieve specified Landing Page objects from HubSpot CMS.

    Use this tool to obtain details of specific Landing Page objects identified by their IDs from the HubSpot CMS. Ideal for fetching batch data on landing pages for content management or analysis."""  # noqa: E501
    request_data: Any = {"inputs": landing_page_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/batch/read",
        method="POST",
        params=remove_none_values({"archived": return_archived_landing_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def clone_landing_page_hubspot(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "ID of the landing page to be cloned. Required for identifying the source page."
    ],
    cloned_landing_page_name: Annotated[
        str | None, "The name to assign to the newly cloned landing page in HubSpot CMS."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/clone_clone'."
]:
    """Clone a landing page in HubSpot CMS.

    This tool duplicates an existing landing page within HubSpot's CMS. It should be used when you need an exact replica of a landing page for further customization or testing."""  # noqa: E501
    request_data: Any = {"cloneName": cloned_landing_page_name, "id": landing_page_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/clone",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_folder_version(
    context: ToolContext,
    folder_id: Annotated[
        str, "The unique identifier for the folder you want to restore in HubSpot CMS."
    ],
    folder_version_id_to_restore: Annotated[
        str, "The ID of the folder version to be restored in HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders/{objectId}/revisions/{revisionId}/restore_restoreFolderPreviousVersion'.",  # noqa: E501
]:
    """Restore a specific version of a folder in HubSpot CMS.

    Call this tool to restore a folder to a specified previous version using its object and revision IDs in HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}/revisions/{revisionId}/restore".format(  # noqa: UP032
            objectId=folder_id, revisionId=folder_version_id_to_restore
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_primary_landing_page_language(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The ID of the landing page to set as primary in the multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/pages/landing-pages/multi-language/set-new-lang-primary_setLangPrimary'.",  # noqa: E501
]:
    """Set a landing page as the primary language for a group.

    Use this tool to set a specific landing page as the primary language of a multi-language group in the HubSpot CMS."""  # noqa: E501
    request_data: Any = {"id": landing_page_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_landing_page_version(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The unique identifier for the landing page you want to restore."
    ],
    landing_page_version_id: Annotated[str, "The ID of the Landing Page version to be restored."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}/restore_restorePreviousVersion'.",  # noqa: E501
]:
    """Restore a specific version of a HubSpot landing page.

    This tool restores a specified version of a landing page in HubSpot CMS. Use this when you need to revert a page to a previous version, providing the necessary identifiers."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}/restore".format(  # noqa: UP032
            objectId=landing_page_id, revisionId=landing_page_version_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_site_page_draft(
    context: ToolContext,
    site_page_id: Annotated[
        str, "The unique identifier for the site page to retrieve its draft version."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/site-pages/{objectId}/draft_getDraftById'.",
]:
    """Retrieve the full draft version of the Site Page.

    Use this tool to get the complete draft version of a specific site page by providing the page's object ID. It should be called when you need to access or review the draft content of a site page in HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/draft".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_site_page_draft(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    site_page_id: Annotated[
        str | None,
        "The unique identifier for the Site Page to update the draft.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/pages/site-pages/{objectId}/draft_updateDraft'.",
]:
    """Update the draft version of a specific site page.

    This tool updates the draft version of a single Site Page object in HubSpot CMS. Use it to modify specific fields of the draft by providing the new values. It is called when there's a need to edit an existing draft page based on its unique identifier.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATESITEPAGEDRAFT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not site_page_id:
        missing_params.append(("site_page_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGEDRAFT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGEDRAFT"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/draft".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_site_pages(
    context: ToolContext,
    page_identifiers: Annotated[
        list[str],
        "An array of strings, each representing an identifier for a Site Page to be retrieved. Use these IDs to specify which Site Pages to access.",  # noqa: E501
    ],
    return_deleted_site_pages: Annotated[
        bool | None, "Set to true to return deleted Site Pages; false to exclude them."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/batch/read_readBatch'.",
]:
    """Retrieve Site Page objects from HubSpot CMS.

    Use this tool to retrieve specific Site Page objects from HubSpot CMS based on identifiers provided in the request. This is useful for accessing detailed information about multiple site pages in a batch."""  # noqa: E501
    request_data: Any = {"inputs": page_identifiers}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/batch/read",
        method="POST",
        params=remove_none_values({"archived": return_deleted_site_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_page_folder_by_id(
    context: ToolContext,
    folder_id: Annotated[str, "The unique identifier for the landing page folder to retrieve."],
    filter_by_property: Annotated[
        str | None,
        "Specify properties to include in the response for the folder object. Leave empty to fetch all properties.",  # noqa: E501
    ] = None,
    include_archived_folders: Annotated[
        bool | None,
        "Specifies whether to include deleted folders in the response. Use 'true' to include them; defaults to 'false'.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/folders/{objectId}_getFolderById'.",  # noqa: E501
]:
    """Retrieve details of a landing page folder by its ID.

    Use this tool to get information about a specific landing page folder in HubSpot CMS by providing its unique ID."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}".format(  # noqa: UP032
            objectId=folder_id
        ),
        method="GET",
        params=remove_none_values({
            "archived": include_archived_folders,
            "property": filter_by_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_landing_page_folder(
    context: ToolContext,
    folder_id: Annotated[
        str,
        "The ID of the folder to be deleted. Provide the specific ID of the CMS landing page folder you wish to delete.",  # noqa: E501
    ],
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/pages/landing-pages/folders/{objectId}_archiveFolder'.",  # noqa: E501
]:
    """Deletes a CMS landing page folder by ID.

    Use this tool to delete a specific CMS landing page folder by providing its object ID. The tool sends a DELETE request to remove the folder."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}".format(  # noqa: UP032
            objectId=folder_id
        ),
        method="DELETE",
        params=remove_none_values({"archived": return_only_archived_results}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_page_folder(
    context: ToolContext,
    content_folder_unique_id: Annotated[
        str, "The unique ID of the landing page content folder to be updated."
    ],
    creation_timestamp: Annotated[
        str,
        "The creation timestamp of the folder in ISO8601 format. Used for reference only, typically not updated.",  # noqa: E501
    ],
    deletion_timestamp_iso8601: Annotated[
        str,
        "The timestamp in ISO8601 format indicating when the content folder was deleted. Required for specifying deletion time.",  # noqa: E501
    ],
    folder_category: Annotated[
        int, "Specify the object type for the folder. Must be LANDING_PAGE."
    ],
    folder_id: Annotated[str, "The unique ID of the landing page folder to be updated."],
    folder_name: Annotated[
        str, "Specify the new name of the landing page folder to display in the app dashboard."
    ],
    parent_folder_id: Annotated[
        int,
        "The ID of the content folder this folder is nested under. Specify this to set a new parent folder.",  # noqa: E501
    ],
    update_timestamp: Annotated[
        str,
        "The timestamp when the folder was last updated, in ISO8601 format. This indicates when changes were made.",  # noqa: E501
    ],
    update_deleted_folders: Annotated[
        bool | None, "Set to true to update folders marked as deleted. Defaults to false."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/pages/landing-pages/folders/{objectId}_updateFolder'.",  # noqa: E501
]:
    """Update specific attributes of a landing page folder.

    Use this tool to update specific attributes of a HubSpot CMS landing page folder, identified by its object ID. Only the column values that need modification should be specified."""  # noqa: E501
    request_data: Any = {
        "deletedAt": deletion_timestamp_iso8601,
        "parentFolderId": parent_folder_id,
        "created": creation_timestamp,
        "name": folder_name,
        "id": content_folder_unique_id,
        "category": folder_category,
        "updated": update_timestamp,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}".format(  # noqa: UP032
            objectId=folder_id
        ),
        method="PATCH",
        params=remove_none_values({"archived": update_deleted_folders}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_ab_test_variation(
    context: ToolContext,
    ab_test_variation_name: Annotated[str, "The name of the A/B test variation to be created."],
    object_test_id: Annotated[str, "The ID of the object to be tested in the A/B test variation."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/ab-test/create-variation_createABTestVariation'.",  # noqa: E501
]:
    """Create a new A/B test variation in HubSpot CMS.

    This tool allows you to create a new A/B test variation for a landing page using HubSpot CMS. It should be called when you need to perform A/B testing by generating different page variations to assess their performance."""  # noqa: E501
    request_data: Any = {"variationName": ab_test_variation_name, "contentId": object_test_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/ab-test/create-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_site_pages_list(
    context: ToolContext,
    created_at_time_filter: Annotated[
        str | None,
        "Return Site Pages created at exactly the specified time. Accepts an exact timestamp in string format.",  # noqa: E501
    ] = None,
    created_before_date_time: Annotated[
        str | None, "Return Site Pages created before this date-time. Use ISO 8601 format."
    ] = None,
    filter_created_after: Annotated[
        str | None,
        "Return Site Pages created after the specified time. Use an ISO 8601 timestamp format.",
    ] = None,
    filter_updated_after: Annotated[
        str | None, "Return site pages updated after this specific time. Use ISO 8601 format."
    ] = None,
    include_deleted_site_pages: Annotated[
        bool | None,
        "Set to true to include deleted Site Pages in the results. Defaults to false if not specified.",  # noqa: E501
    ] = None,
    include_property_details: Annotated[
        str | None,
        "Specify the properties to include in the Site Pages results. Leave empty to include all properties.",  # noqa: E501
    ] = None,
    last_updated_before: Annotated[
        str | None, "Return site pages last updated before this datetime. Format: ISO 8601 string."
    ] = None,
    last_updated_exact_time: Annotated[
        str | None,
        "Return site pages last updated at exactly the specified time in ISO 8601 format.",
    ] = None,
    maximum_results: Annotated[
        int | None, "The maximum number of site pages to return. Default is 100."
    ] = None,
    next_page_cursor: Annotated[
        str | None, "Cursor token for paged results; use `paging.next.after` for the next set."
    ] = None,
    sort_fields_for_results: Annotated[
        list[str] | None,
        "Specify fields for sorting site pages. Options: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Default is `createdAt`.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/pages/site-pages_getPage'."
]:
    """Retrieve a list of site pages with filtering options.

    Use this tool to get a list of site pages, supporting both paging and filtering. Ideal for integrations that need to examine pages or use external services to suggest edits."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages",
        method="GET",
        params=remove_none_values({
            "createdAt": created_at_time_filter,
            "createdAfter": filter_created_after,
            "createdBefore": created_before_date_time,
            "updatedAt": last_updated_exact_time,
            "updatedAfter": filter_updated_after,
            "updatedBefore": last_updated_before,
            "sort": sort_fields_for_results,
            "after": next_page_cursor,
            "limit": maximum_results,
            "archived": include_deleted_site_pages,
            "property": include_property_details,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_site_page(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/pages/site-pages_create'."
]:
    """Create a new site page in HubSpot CMS.

    This tool is used to create a new site page in HubSpot CMS. It should be called when a user wants to add a new page to their website using the HubSpot platform.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATESITEPAGE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATESITEPAGE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATESITEPAGE"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_landing_page_to_language_group(
    context: ToolContext,
    designated_language_to_add: Annotated[
        str,
        "The language of the landing page to add to a multi-language group. Expect a language code.",  # noqa: E501
    ],
    object_id_for_language_group: Annotated[
        str, "ID of the landing page to add to the multi-language group."
    ],
    primary_language_object_id: Annotated[
        str,
        "Provide the ID of the primary language object in the multi-language group to which the landing page will be attached.",  # noqa: E501
    ],
    primary_language: Annotated[
        str | None,
        "Specifies the primary language of the multi-language group to which the landing page will be attached.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/multi-language/attach-to-lang-group_attachToLangGroup'.",  # noqa: E501
]:
    """Attach a landing page to a multi-language group.

    Use this tool to associate a landing page with an existing multi-language group, enabling better management of language variations within HubSpot CMS."""  # noqa: E501
    request_data: Any = {
        "language": designated_language_to_add,
        "id": object_id_for_language_group,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_language_variation(
    context: ToolContext,
    content_id_to_clone: Annotated[
        str, "ID of the site page content to clone for creating a language variation."
    ],
    primary_content_language: Annotated[
        str | None,
        "Specify the language of the primary content to clone for creating the variation.",
    ] = None,
    target_language: Annotated[
        str | None, "Target language code for the new site page variant, e.g., 'fr' for French."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/multi-language/create-language-variation_createLangVariation'.",  # noqa: E501
]:
    """Create a new language variation for a site page.

    Use this tool to create a new language variation from an existing site page in HubSpot CMS. It helps in managing multilingual content efficiently by generating a language-specific page version."""  # noqa: E501
    request_data: Any = {
        "language": target_language,
        "id": content_id_to_clone,
        "primaryLanguage": primary_content_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/multi-language/create-language-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_previous_landing_page_version(
    context: ToolContext,
    landing_page_id: Annotated[str, "The unique identifier for the landing page."],
    landing_page_version_id: Annotated[str, "The ID of the landing page version to retrieve."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}_getPreviousVersion'.",  # noqa: E501
]:
    """Retrieve a previous version of a Landing Page.

    Use this tool to obtain a prior version of a landing page from HubSpot's CMS using specific object and revision IDs."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}".format(  # noqa: UP032
            objectId=landing_page_id, revisionId=landing_page_version_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def end_ab_test_select_winner(
    context: ToolContext,
    test_id_to_end: Annotated[str, "ID of the A/B test to be ended."],
    winner_object_id: Annotated[
        str, "ID of the landing page variant to designate as the winner of the A/B test."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/ab-test/end_endActiveABTest'.",  # noqa: E501
]:
    """End an active A/B test and designate a winner.

    Use this tool to end an ongoing A/B test for landing pages and select the winning variant. Ideal for finalizing tests and applying successful strategies."""  # noqa: E501
    request_data: Any = {"winnerId": winner_object_id, "abTestId": test_id_to_end}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/ab-test/end",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def publish_landing_page_draft(
    context: ToolContext,
    landing_page_id: Annotated[str, "The ID of the landing page whose draft will be pushed live."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/{objectId}/draft/push-live_pushLive'.",  # noqa: E501
]:
    """Publish changes from a landing page draft to live.

    Use this tool to apply any updates made in the draft version of a HubSpot CMS landing page to its live version. Ideal for finalizing and publishing edits."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/draft/push-live".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_landing_page_from_language_group(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The ID of the landing page to detach from the multi-language group in HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/multi-language/detach-from-lang-group_detachFromLangGroup'.",  # noqa: E501
]:
    """Detach a landing page from a multi-language group.

    Call this tool to detach a specific landing page from its multi-language group in HubSpot CMS. Useful for managing language-specific versions of pages individually."""  # noqa: E501
    request_data: Any = {"id": landing_page_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def new_ab_test_variation(
    context: ToolContext,
    ab_test_variation_name: Annotated[
        str, "The name for the new A/B test variation to be created."
    ],
    object_id_to_test: Annotated[str, "ID of the object to be tested in the A/B test variation."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/ab-test/create-variation_createABTestVariation'.",  # noqa: E501
]:
    """Create a new A/B test variation in HubSpot CMS.

    This tool creates a new A/B test variation for a site page in HubSpot CMS using the provided details."""  # noqa: E501
    request_data: Any = {"variationName": ab_test_variation_name, "contentId": object_id_to_test}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/ab-test/create-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def schedule_landing_page(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The unique identifier for the landing page to be scheduled for publication."
    ],
    publication_date: Annotated[
        str, "The date when the landing page should be published. Use the format YYYY-MM-DD."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/schedule_schedule'.",
]:
    """Schedule a landing page for publication.

    This tool schedules a landing page to be published, using HubSpot CMS. It is used when you need to set a specific time for your landing page to go live."""  # noqa: E501
    request_data: Any = {"publishDate": publication_date, "id": landing_page_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/schedule",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_page_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/multi-language/update-languages_updateLangs'.",  # noqa: E501
]:
    """Update languages for landing pages in a multi-language group.

    This tool explicitly sets new languages for each landing page within a specified multi-language group. Use it when you need to modify the language settings of your HubSpot CMS landing pages.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGELANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGELANGUAGES"]
                + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n"
                + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGELANGUAGES"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGELANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_site_page_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/multi-language/update-languages_updateLangs'.",  # noqa: E501
]:
    """Set new languages for site pages in a multi-language group.

    Use this tool to explicitly set new languages for each site page within a multi-language group. Ideal for updating or managing different language versions of your website's pages.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATESITEPAGELANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGELANGUAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGELANGUAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESITEPAGELANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def rerun_previous_ab_test(
    context: ToolContext,
    test_id_to_rerun: Annotated[
        str, "Provide the ID of the test you want to rerun on the landing page."
    ],
    variation_id: Annotated[
        str,
        "Provide the ID of the object you wish to reactivate as a test variation. This ID is necessary to specify which variation of the landing page you intend to rerun in the A/B test.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/ab-test/rerun_rerunPreviousABTest'.",  # noqa: E501
]:
    """Rerun a previous A/B test on a landing page.

    Initiate the rerun of a previous A/B test for a landing page in HubSpot CMS. Use this tool when you need to repeat an A/B test to validate or compare results."""  # noqa: E501
    request_data: Any = {"variationId": variation_id, "abTestId": test_id_to_rerun}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/ab-test/rerun",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_landing_page_folders(
    context: ToolContext,
    folder_identifiers: Annotated[
        list[str], "An array of strings representing the identifiers of the folders to be deleted."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders/batch/archive_archiveFolders'.",  # noqa: E501
]:
    """Delete specified landing page folders in HubSpot CMS.

    Use this tool to delete landing page folders in the HubSpot CMS as specified in the request. It should be called when you need to remove these folders from the CMS."""  # noqa: E501
    request_data: Any = {"inputs": folder_identifiers}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_site_pages_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/batch/create_createBatch'.",
]:
    """Create multiple site page objects in batch.

    Use this tool to create multiple site page objects at once based on the details provided in the request body.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATESITEPAGESBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATESITEPAGESBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATESITEPAGESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/batch/create",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_cms_site_pages(
    context: ToolContext,
    page_ids_to_delete: Annotated[
        list[str], "An array of strings representing the IDs of the Site Page objects to delete."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/batch/archive_archiveBatch'.",
]:
    """Delete specified Site Page objects in the CMS.

    This tool deletes the Site Page objects specified in the request body on the HubSpot CMS. It is used when you need to remove multiple site pages at once. This operation is different from archiving in the dashboard."""  # noqa: E501
    request_data: Any = {"inputs": page_ids_to_delete}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_page_folders(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_include_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived folder results; false to return all folders.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders/batch/update_updateFolders'.",  # noqa: E501
]:
    """Update multiple HubSpot CMS landing page folders.

    Use this tool to update the Folder objects for landing pages in HubSpot CMS as identified in the request body.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEFOLDERS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEFOLDERS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEFOLDERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGEFOLDERS"],
        params=remove_none_values({"archived": only_include_archived_results}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def clone_site_page(
    context: ToolContext,
    object_id_to_clone: Annotated[str, "ID of the site page to be cloned in HubSpot CMS."],
    clone_name: Annotated[str | None, "The desired name for the cloned site page object."] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/pages/site-pages/clone_clone'."
]:
    """Clone an existing site page in HubSpot CMS.

    This tool is used to clone an existing site page within the HubSpot CMS. It is useful when duplicating pages for updates or creating similar content quickly."""  # noqa: E501
    request_data: Any = {"cloneName": clone_name, "id": object_id_to_clone}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/clone",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_primary_language_for_site_page(
    context: ToolContext,
    page_id_to_set_primary: Annotated[
        str, "The ID of the site page to set as the primary language in the multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/pages/site-pages/multi-language/set-new-lang-primary_setLangPrimary'.",  # noqa: E501
]:
    """Set a site page as the primary language of its group.

    This tool is used to designate a site page as the primary language page in a multi-language group within HubSpot CMS. It should be called when a user needs to configure language settings for site pages."""  # noqa: E501
    request_data: Any = {"id": page_id_to_set_primary}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_folder_previous_version(
    context: ToolContext,
    folder_id: Annotated[
        str, "The unique identifier for the folder whose previous version you want to retrieve."
    ],
    folder_version_id: Annotated[
        str, "The unique identifier for the folder version to be retrieved."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/folders/{objectId}/revisions/{revisionId}_getFolderPreviousVersion'.",  # noqa: E501
]:
    """Retrieve a previous version of a folder in HubSpot CMS.

    Use this tool to obtain information about a specific previous version of a folder within HubSpot's CMS. Call this tool when you need to access historical data of a folder's version for reference or comparison."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/{objectId}/revisions/{revisionId}".format(  # noqa: UP032
            objectId=folder_id, revisionId=folder_version_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def reset_landing_page_draft(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The ID of the Landing Page whose draft will be reset to the live version."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/{objectId}/draft/reset_resetDraft'.",  # noqa: E501
]:
    """Reset a landing page draft to its live version.

    Use this tool to discard any edits made to a landing page draft and reset it to match the current live version, ensuring no changes made in the draft are preserved."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/draft/reset".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_landing_page_by_id(
    context: ToolContext,
    landing_page_id: Annotated[str, "The unique identifier of the landing page to be retrieved."],
    include_archived: Annotated[
        bool | None,
        "Include deleted landing pages in the response if set to true. Defaults to false.",
    ] = None,
    landing_page_property: Annotated[
        str | None,
        "Specify which properties of the landing page to retrieve, such as 'title' or 'url'. Leave blank to fetch all.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/landing-pages/{objectId}_getById'.",
]:
    """Retrieve details of a specific landing page by ID.

    Use this tool to fetch the details of a landing page from HubSpot CMS by providing the landing page's unique ID."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="GET",
        params=remove_none_values({
            "archived": include_archived,
            "property": landing_page_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_landing_page(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The unique identifier of the landing page to delete in HubSpot CMS."
    ],
    return_archived_results_only: Annotated[
        bool | None, "Set to true to only return results that have been archived."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/pages/landing-pages/{objectId}_archive'.",
]:
    """Delete a specified landing page by its ID.

    Use this tool to delete a landing page in HubSpot CMS by providing the landing page's ID."""
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="DELETE",
        params=remove_none_values({"archived": return_archived_results_only}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_landing_page(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    landing_page_id: Annotated[
        str | None,
        "The unique ID of the landing page to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    update_archived_pages: Annotated[
        bool | None,
        "Set to true to update archived (deleted) Landing Pages. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/pages/landing-pages/{objectId}_update'.",
]:
    """Update specific fields of a landing page by ID.

    This tool updates a landing page in HubSpot CMS by specifying only the fields you wish to modify, identified by the page's ID.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not landing_page_id:
        missing_params.append(("landing_page_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATELANDINGPAGE"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}".format(  # noqa: UP032
            objectId=landing_page_id
        ),
        method="PATCH",
        params=remove_none_values({"archived": update_archived_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def publish_site_page(
    context: ToolContext,
    site_page_id: Annotated[
        str,
        "The ID of the Site Page whose draft will be published live. Ensure it corresponds to the correct page in HubSpot CMS.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/{objectId}/draft/push-live_pushLive'.",  # noqa: E501
]:
    """Publish draft changes to live site page.

    Use this tool to apply draft changes of a site page to its live version. It should be called when you want to make the draft content public on the HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/draft/push-live".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_multilanguage_landing_page(
    context: ToolContext,
    content_id_to_clone: Annotated[
        str,
        "The ID of the landing page content to be cloned for creating a new language variation.",
    ],
    primary_language_of_content: Annotated[
        str | None,
        "Language code of the primary content to be cloned for creating the new language variation.",  # noqa: E501
    ] = None,
    target_language: Annotated[
        str | None, "Specify the target language for the new landing page variant."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/multi-language/create-language-variation_createLangVariation'.",  # noqa: E501
]:
    """Create a new language variation for a landing page.

    This tool creates a new language variation from an existing landing page in HubSpot CMS. Use it to generate multilingual versions of your landing pages."""  # noqa: E501
    request_data: Any = {
        "language": target_language,
        "id": content_id_to_clone,
        "primaryLanguage": primary_language_of_content,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/multi-language/create-language-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_landing_pages(
    context: ToolContext,
    landing_page_ids: Annotated[
        list[str],
        "An array of strings representing the IDs of landing pages to delete permanently.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/batch/archive_archiveBatch'.",
]:
    """Deletes specified HubSpot landing pages permanently.

    Use this tool to permanently delete landing page objects in HubSpot CMS. It's different from archiving pages via the dashboard. Ensure you provide the specific page identifiers in the request body."""  # noqa: E501
    request_data: Any = {"inputs": landing_page_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_landing_page_draft(
    context: ToolContext,
    landing_page_id: Annotated[
        str, "The ID of the landing page to restore a specific version to draft."
    ],
    landing_page_version_id_to_restore: Annotated[
        int, "The ID of the landing page version you want to restore as the new draft."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}/restore-to-draft_restorePreviousVersionToDraft'.",  # noqa: E501
]:
    """Restore a specified landing page version to draft.

    This tool restores a specified version of a landing page as the new draft version. Use it when you need to revert changes on a landing page and set a previous version as the current draft."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/{objectId}/revisions/{revisionId}/restore-to-draft".format(  # noqa: UP032
            objectId=landing_page_id, revisionId=landing_page_version_id_to_restore
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restart_ab_test(
    context: ToolContext,
    ab_test_id: Annotated[
        str, "The unique ID of the A/B test you wish to rerun on your HubSpot CMS site pages."
    ],
    test_variation_id: Annotated[
        str, "ID of the object to reactivate as a test variation for the A/B test."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/ab-test/rerun_rerunPreviousABTest'.",  # noqa: E501
]:
    """Rerun a previous A/B test to gain new insights.

    Use this tool to rerun an A/B test on your HubSpot CMS site pages. It should be called when you want to repeat a previous test for additional insights or validation of results."""  # noqa: E501
    request_data: Any = {"variationId": test_variation_id, "abTestId": ab_test_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/ab-test/rerun",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def reset_hubspot_page_draft(
    context: ToolContext,
    site_page_id: Annotated[
        str, "The ID of the HubSpot Site Page whose draft will be reset to the live version."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/site-pages/{objectId}/draft/reset_resetDraft'.",  # noqa: E501
]:
    """Resets HubSpot CMS draft to the live version.

    This tool discards any edits made to a HubSpot CMS page draft and reverts it to the last published live version. Use it to undo changes that you no longer wish to keep."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}/draft/reset".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_site_page_by_id(
    context: ToolContext,
    site_page_id: Annotated[str, "The unique identifier for the Site Page to retrieve."],
    return_archived_site_pages: Annotated[
        bool | None, "Boolean indicating whether to return deleted Site Pages. Defaults to 'false'."
    ] = None,
    site_page_property: Annotated[
        str | None,
        "Specify the property of the Site Page to return. Leave empty to retrieve all properties.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/pages/site-pages/{objectId}_getById'.",
]:
    """Retrieve Site Page details by ID.

    This tool retrieves the details of a Site Page object identified by its ID. It should be called when you need information about a specific Site Page."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="GET",
        params=remove_none_values({
            "archived": return_archived_site_pages,
            "property": site_page_property,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_site_page(
    context: ToolContext,
    site_page_id: Annotated[
        str, "The unique identifier for the site page to be deleted in HubSpot CMS."
    ],
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only the pages that have been archived."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/pages/site-pages/{objectId}_archive'.",
]:
    """Delete a specified site page in HubSpot CMS.

    Use this tool to delete a specific site page in HubSpot CMS by providing the page's object ID. This should be called when a page needs to be archived or removed permanently."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="DELETE",
        params=remove_none_values({"archived": return_only_archived_results}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_site_page(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    site_page_id: Annotated[
        str | None,
        "The unique identifier for the Site Page to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    update_deleted_site_pages: Annotated[
        bool | None,
        "Boolean to specify whether to update deleted Site Pages. Defaults to `false`.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/pages/site-pages/{objectId}_update'.",
]:
    """Update specific fields of a HubSpot site page.

    This tool performs a sparse update on a single site page in HubSpot CMS, modifying only the specified fields of the page identified by its ID.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATESITEPAGE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not site_page_id:
        missing_params.append(("site_page_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGE"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATESITEPAGE"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/site-pages/{objectId}".format(  # noqa: UP032
            objectId=site_page_id
        ),
        method="PATCH",
        params=remove_none_values({"archived": update_deleted_site_pages}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_folder_objects(
    context: ToolContext,
    folder_identifiers: Annotated[
        list[str], "An array of strings identifying folder objects to update in HubSpot CMS."
    ],
    include_archived_folders: Annotated[
        bool | None,
        "Indicate whether to include archived folders in the results. Defaults to `false`.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/pages/landing-pages/folders/batch/read_readFolders'.",  # noqa: E501
]:
    """Update specified folder objects in HubSpot CMS.

    Use this tool to update the folder objects identified in the request body within the HubSpot CMS platform. Useful for batch modifications of landing page folder metadata."""  # noqa: E501
    request_data: Any = {"inputs": folder_identifiers}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/pages/landing-pages/folders/batch/read",
        method="POST",
        params=remove_none_values({"archived": include_archived_folders}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def schedule_blog_post(
    context: ToolContext,
    blog_post_id: Annotated[str, "The ID of the blog post to be scheduled for future publication."],
    scheduled_publish_date: Annotated[
        str,
        "The date and time when the blog post should be published, formatted as a string (e.g., '2023-12-31T23:59:59').",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts/schedule_schedule'."
]:
    """Schedule a blog post for future publication.

    This tool is used to schedule a HubSpot CMS blog post to be published at a specific time. It should be called when you want to set a future publication date for a blog post."""  # noqa: E501
    request_data: Any = {"publishDate": scheduled_publish_date, "id": blog_post_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/schedule",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_posts_by_id(
    context: ToolContext,
    blog_post_ids: Annotated[
        list[str], "A list of blog post IDs to retrieve. Each ID should be a string."
    ],
    include_archived_posts: Annotated[
        bool | None, "Include deleted blog posts if true. Defaults to `false`."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts/batch/read_read'."
]:
    """Retrieve a batch of blog posts by their IDs.

    This tool fetches multiple blog posts from HubSpot CMS based on the IDs provided in the request body. Use it when you need to access specific blog posts using their identifiers."""  # noqa: E501
    request_data: Any = {"inputs": blog_post_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/batch/read",
        method="POST",
        params=remove_none_values({"archived": include_archived_posts}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_post_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/multi-language/update-languages_updateLangs'.",  # noqa: E501
]:
    """Set new languages for multi-language blog posts.

    Use this tool to explicitly set or update the languages for each post in a multi-language group within HubSpot's CMS. Ideal for managing content across different languages.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTLANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTLANGUAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTLANGUAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/posts/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTLANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_batch_blog_posts(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts/batch/create_create'."
]:
    """Create multiple blog posts in a single request.

    This tool is used to create a batch of blog posts by specifying their content in the request body. It should be called when you need to publish multiple blog posts at once.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBATCHBLOGPOSTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBATCHBLOGPOSTS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBATCHBLOGPOSTS"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/batch/create",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_posts_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    update_archived_posts: Annotated[
        bool | None,
        "Indicates whether to update deleted blog posts. Defaults to `false`.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts/batch/update_update'."
]:
    """Update a batch of blog posts in the CMS.

    Use this tool to update multiple blog posts simultaneously in the HubSpot CMS. Ideal for making bulk edits to post content or metadata.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTSBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/posts/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTSBATCH"],
        params=remove_none_values({"archived": update_archived_posts}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_blog_post_to_draft(
    context: ToolContext,
    blog_post_id: Annotated[
        str,
        "The unique identifier for the blog post to be restored to a draft version. This is the same as the blog post's current ID in the HubSpot CMS.",  # noqa: E501
    ],
    version_to_restore_id: Annotated[
        int, "The ID of the blog post version to revert to the draft state."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}/restore-to-draft_restorePreviousVersionToDraft'.",  # noqa: E501
]:
    """Restore a previous blog post version to draft.

    Use this tool to revert a specified version of a blog post to the draft state in HubSpot CMS. This is useful for undoing changes or reverting to a previous version of content."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}/restore-to-draft".format(  # noqa: UP032
            objectId=blog_post_id, revisionId=version_to_restore_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_post_draft(
    context: ToolContext,
    blog_post_id: Annotated[str, "The ID of the blog post to retrieve the draft version."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/blogs/posts/{objectId}/draft_getDraftById'.",
]:
    """Retrieve the full draft version of a blog post.

    Use this tool to get the draft version of a specific blog post by providing the post's object ID."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/draft".format(  # noqa: UP032
            objectId=blog_post_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_post_draft(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    blog_post_id: Annotated[
        str | None,
        "The ID of the blog post draft to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/blogs/posts/{objectId}/draft_updateDraft'.",
]:
    """Update specific fields of a blog post draft.

    Use this tool to partially update the draft version of a blog post by specifying the blog post ID and the fields you wish to update.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTDRAFT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not blog_post_id:
        missing_params.append(("blog_post_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTDRAFT"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOSTDRAFT"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/draft".format(  # noqa: UP032
            objectId=blog_post_id
        ),
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_previous_blog_version(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The unique identifier for the blog post whose previous version is to be retrieved."
    ],
    version_id: Annotated[str, "The ID of the specific blog post version to retrieve."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}_getPreviousVersion'.",  # noqa: E501
]:
    """Retrieve a previous version of a blog post.

    This tool retrieves details of a specific previous version of a blog post. It is useful for accessing and reviewing earlier versions of content."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}".format(  # noqa: UP032
            objectId=blog_post_id, revisionId=version_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def new_language_blog_variation(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The ID of the blog post you want to clone into a new language variation."
    ],
    target_language: Annotated[
        str | None,
        "The language code for the target language of the new blog post variation (e.g., 'fr' for French).",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/multi-language/create-language-variation_createLangVariation'.",  # noqa: E501
]:
    """Create a new language variation from an existing blog post.

    Use this tool to generate a new language version of an existing blog post on HubSpot CMS. It's useful for expanding the reach of content to different language audiences."""  # noqa: E501
    request_data: Any = {"language": target_language, "id": blog_post_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/multi-language/create-language-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def clone_blog_post(
    context: ToolContext,
    blog_post_id: Annotated[
        str,
        "ID of the blog post to be cloned. This identifies the specific blog post you want to duplicate.",  # noqa: E501
    ],
    cloned_blog_post_name: Annotated[
        str | None,
        "The name for the newly cloned blog post. This is how the cloned post will be titled or identified.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts/clone_clone'."
]:
    """Creates a copy of an existing blog post.

    This tool clones an existing blog post, creating a new copy with the same content. It should be used when you want to duplicate a post for editing or reuse."""  # noqa: E501
    request_data: Any = {"cloneName": cloned_blog_post_name, "id": blog_post_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/clone",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_posts(
    context: ToolContext,
    created_after: Annotated[
        str | None,
        "Return blog posts created after the specified time. Use ISO 8601 format for the date-time.",  # noqa: E501
    ] = None,
    created_before: Annotated[
        str | None, "Only return blog posts created before the specified time in ISO 8601 format."
    ] = None,
    filter_by_creation_time: Annotated[
        str | None, "Return only blog posts created at the specified time in ISO 8601 format."
    ] = None,
    filter_by_update_after: Annotated[
        str | None,
        "Only return blog posts last updated after the specified timestamp. Use a format such as ISO 8601 for date and time.",  # noqa: E501
    ] = None,
    include_archived_posts: Annotated[
        bool | None,
        "Specify true to include archived (deleted) blog posts in the results. Defaults to false.",
    ] = None,
    max_results: Annotated[
        int | None, "The maximum number of blog post results to return. Default is 20."
    ] = None,
    pagination_cursor_token: Annotated[
        str | None,
        "The cursor token to retrieve the next set of blog post results. Obtain from `paging.next.after` in a previous response.",  # noqa: E501
    ] = None,
    return_specific_properties: Annotated[
        str | None,
        "Comma-separated list of specific fields to return for each blog post (e.g., 'title,author,date'). Leave empty to return all fields.",  # noqa: E501
    ] = None,
    sort_fields_for_results: Annotated[
        list[str] | None,
        "An array specifying fields to sort blog posts. Use fields like `createdAt`, `name`, `updatedAt`, `createdBy`, `updatedBy`.",  # noqa: E501
    ] = None,
    updated_at_exact: Annotated[
        str | None, "Return blog posts last updated at the exact specified time in ISO 8601 format."
    ] = None,
    updated_before_date: Annotated[
        str | None,
        "Return blog posts last updated before this date. Use ISO 8601 format (e.g., '2023-09-04T15:00:00Z').",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/posts_getPage'."]:
    """Retrieve all blog posts with optional paging and filtering.

    Use this tool to access and browse all blog posts available, with additional options for paging and filtering. Ideal for integrating services that need to ingest blog content and suggest edits."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts",
        method="GET",
        params=remove_none_values({
            "createdAt": filter_by_creation_time,
            "createdAfter": created_after,
            "createdBefore": created_before,
            "updatedAt": updated_at_exact,
            "updatedAfter": filter_by_update_after,
            "updatedBefore": updated_before_date,
            "sort": sort_fields_for_results,
            "after": pagination_cursor_token,
            "limit": max_results,
            "archived": include_archived_posts,
            "property": return_specific_properties,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_post(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/posts_create'."]:
    """Create a new blog post with specified content.

    Use this tool to create a new blog post by specifying its content in the request. Ideal for automating blog publishing processes.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBLOGPOST"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGPOST"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGPOST"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def restore_blog_post_version(
    context: ToolContext,
    blog_post_id: Annotated[str, "The ID of the blog post to be restored to a previous version."],
    version_to_restore_id: Annotated[
        str, "The unique identifier of the blog post version to which you want to revert."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}/restore_restorePreviousVersion'.",  # noqa: E501
]:
    """Restore a blog post to a previous version.

    Use this tool to revert a blog post to an earlier saved version by providing the specific object ID and revision ID."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/revisions/{revisionId}/restore".format(  # noqa: UP032
            objectId=blog_post_id, revisionId=version_to_restore_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_blog_post_language_group(
    context: ToolContext,
    object_id_to_detach_from_language_group: Annotated[
        str, "ID of the blog post to be removed from the multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/multi-language/detach-from-lang-group_detachFromLangGroup'.",  # noqa: E501
]:
    """Detach a blog post from a multi-language group.

    Use this tool to detach a specific blog post from its associated multi-language group in HubSpot CMS. Useful when you need to separately manage or remove language-specific versions of a post."""  # noqa: E501
    request_data: Any = {"id": object_id_to_detach_from_language_group}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def publish_blog_post(
    context: ToolContext,
    blog_post_id: Annotated[str, "The unique identifier of the blog post to be published live."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/{objectId}/draft/push-live_pushLive'.",  # noqa: E501
]:
    """Publish a draft blog post to make it live.

    Use this tool to publish the draft version of a blog post, sending its content to the live page."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/draft/push-live".format(  # noqa: UP032
            objectId=blog_post_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_blog_post(
    context: ToolContext,
    blog_post_ids_to_delete: Annotated[
        list[str],
        "An array of blog post IDs to delete. Each ID should be a string representing a unique blog post.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/batch/archive_archive'.",
]:
    """Delete a blog post by its unique ID.

    Use this tool to delete a blog post using its ID. Note that this action permanently removes the post, unlike the in-app archive function. For dashboard archiving, use an update with `archivedInDashboard` set to `true`."""  # noqa: E501
    request_data: Any = {"inputs": blog_post_ids_to_delete}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def reset_blog_post_draft(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The unique ID of the blog post draft that you want to reset to the published version."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/{objectId}/draft/reset_resetDraft'.",
]:
    """Resets a blog post draft to the currently published content.

    Use this tool to discard all changes in a blog post draft and return the draft to its last published state. This can be useful when you want to undo all edits made in draft since the last publication."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/draft/reset".format(  # noqa: UP032
            objectId=blog_post_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_blog_post_to_language_group(
    context: ToolContext,
    designated_language: Annotated[
        str,
        "Specify the language code (e.g., 'en', 'fr', 'es') for the blog post to be added to the multi-language group.",  # noqa: E501
    ],
    object_id: Annotated[str, "The ID of the blog post to attach to a multi-language group."],
    primary_language_object_id: Annotated[
        str, "ID of the primary language object in the multi-language group."
    ],
    primary_language: Annotated[
        str | None,
        "Primary language of the multi-language group. Specify in ISO language code format (e.g., 'en', 'fr').",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/posts/multi-language/attach-to-lang-group_attachToLangGroup'.",  # noqa: E501
]:
    """Attach a blog post to a multi-language group in HubSpot CMS.

    Use this tool to attach a specific blog post to a multi-language group in HubSpot CMS, enabling content localization and management."""  # noqa: E501
    request_data: Any = {
        "language": designated_language,
        "id": object_id,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_primary_language_blog_post(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The ID of the blog post to set as primary in its multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/blogs/posts/multi-language/set-new-lang-primary_setLangPrimary'.",  # noqa: E501
]:
    """Set the primary language of a blog post in a multi-language group.

    This tool updates the primary language of a multi-language group to match the language of the specified blog post by its ID. It is useful when managing multilingual content on HubSpot CMS blogs."""  # noqa: E501
    request_data: Any = {"id": blog_post_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_previous_blog_post_versions(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The unique ID of the blog post for retrieving its previous versions in HubSpot CMS."
    ],
    before_timestamp: Annotated[
        str | None, "The timestamp to retrieve versions updated before this time."
    ] = None,
    maximum_results_limit: Annotated[
        int | None, "The maximum number of blog post versions to return. Default is 100."
    ] = None,
    next_page_cursor: Annotated[
        str | None,
        "The cursor token to retrieve the next set of blog post versions, obtained from `paging.next.after`.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/blogs/posts/{objectId}/revisions_getPreviousVersions'.",  # noqa: E501
]:
    """Retrieve all previous versions of a blog post in HubSpot.

    Call this tool to access historical versions of a specific blog post using its 'objectId' in HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}/revisions".format(  # noqa: UP032
            objectId=blog_post_id
        ),
        method="GET",
        params=remove_none_values({
            "after": next_page_cursor,
            "before": before_timestamp,
            "limit": maximum_results_limit,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_post_by_id(
    context: ToolContext,
    blog_post_id: Annotated[
        str, "The unique identifier of the blog post to retrieve from HubSpot CMS."
    ],
    include_archived_posts: Annotated[
        bool | None,
        "If true, includes deleted (archived) blog posts in the retrieval. Defaults to false.",
    ] = None,
    return_specific_properties: Annotated[
        str | None, "Comma-separated list of specific properties to retrieve from the blog post."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/posts/{objectId}_getById'."
]:
    """Retrieve a blog post by its ID from HubSpot CMS.

    This tool retrieves the details of a specific blog post using its ID from the HubSpot CMS, ideal for accessing individual post content."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}".format(objectId=blog_post_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "archived": include_archived_posts,
            "property": return_specific_properties,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def remove_blog_post(
    context: ToolContext,
    blog_post_id: Annotated[
        str,
        "The unique ID of the blog post to delete. This ID identifies which specific post will be removed.",  # noqa: E501
    ],
    only_archived_results: Annotated[
        bool | None, "Set to true to return only deleted (archived) blog posts."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/blogs/posts/{objectId}_archive'.",
]:
    """Delete a blog post by its ID.

    This tool removes a specific blog post using its unique ID. Use it if you need to delete content from the blog."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}".format(objectId=blog_post_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({"archived": only_archived_results}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_post(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    blog_post_id: Annotated[
        str | None,
        "The unique identifier of the blog post to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    update_archived_posts: Annotated[
        bool | None,
        "Set to true to update deleted (archived) blog posts. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/cms/v3/blogs/posts/{objectId}_update'."
]:
    """Update specific fields of a blog post by ID.

    This tool updates specific fields of a blog post using its ID, requiring only the values that need to be changed. Ideal for making partial updates to existing blog content.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path, query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path, query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGPOST"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path, query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not blog_post_id:
        missing_params.append(("blog_post_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOST"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGPOST"] + "\n```"
            ),
        ) from e

    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/posts/{objectId}".format(objectId=blog_post_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({"archived": update_archived_posts}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_indexed_data_by_content_id(
    context: ToolContext,
    document_id: Annotated[
        str, "ID of the target document when searching for indexed properties in HubSpot CMS."
    ],
    document_type: Annotated[
        str | None,
        "Specify the document type, such as `SITE_PAGE`, `BLOG_POST`, or `KNOWLEDGE_ARTICLE`.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/site-search/indexed-data/{contentId}_getById'.",
]:
    """Retrieve all indexed data for a specific document by ID.

    Use this tool to obtain indexed data for a specific document in the HubSpot CMS, such as a page, blog post, or HubDB row, for debugging purposes."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/site-search/indexed-data/{contentId}".format(  # noqa: UP032
            contentId=document_id
        ),
        method="GET",
        params=remove_none_values({"type": document_type}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def search_website_content(
    context: ToolContext,
    blog_ids_to_search: Annotated[
        list[int] | None,
        "List of blog IDs to search within. Allows searching multiple blogs by their IDs.",
    ] = None,
    boost_recent_time_window: Annotated[
        str | None,
        "A relative time window to boost scores of documents published within this period. Use '10d' for 10 days. Supported units: ms, s, m, h, d. Applicable to blog posts only.",  # noqa: E501
    ] = None,
    content_language_code: Annotated[
        str | None,
        "Specifies the language of content to be searched using a valid ISO 639-1 code (e.g. 'es' for Spanish).",  # noqa: E501
    ] = None,
    content_type_filters: Annotated[
        list[str] | None,
        "List of content types to search, such as SITE_PAGE, LANDING_PAGE, BLOG_POST, LISTING_PAGE, and KNOWLEDGE_ARTICLE.",  # noqa: E501
    ] = None,
    hubdb_query_filter: Annotated[
        str | None, "Specify a HubDB query to filter search results in the specified table."
    ] = None,
    hubdb_table_id: Annotated[
        int | None,
        "Specifies a specific HubDB table to search. Only results from this table are returned. Can be combined with 'hubdb_query' for further filtering.",  # noqa: E501
    ] = None,
    invert_path_prefix_filter: Annotated[
        bool | None,
        "Set to 'false' to apply the normal behavior of the pathPrefix filter; 'true' to invert it.",  # noqa: E501
    ] = None,
    maximum_boost_limit: Annotated[
        float | None,
        "Specifies the maximum amount a result will be boosted based on its view count. Defaults to 5.0.",  # noqa: E501
    ] = None,
    pagination_offset: Annotated[
        int | None,
        "Integer used to page through results. Use the offset from the previous request to access the next set of results.",  # noqa: E501
    ] = None,
    path_prefixes: Annotated[
        list[str] | None,
        "An array of strings to filter search results by URL path prefix. Only returns results with paths starting with specified prefixes.",  # noqa: E501
    ] = None,
    popularity_boost: Annotated[
        float | None,
        "Specify the boost factor for a result based on its view count. Defaults to 1.0.",
    ] = None,
    result_length: Annotated[
        str | None, "Specifies whether search results should be detailed (LONG) or brief (SHORT)."
    ] = None,
    results_limit: Annotated[
        int | None,
        "The number of search results to return. Defaults to 10 and has a maximum of 100.",
    ] = None,
    search_domains: Annotated[
        list[str] | None,
        "List of domains to match search results for. Multiple domains can be included by separating them with '&'.",  # noqa: E501
    ] = None,
    search_properties: Annotated[
        list[str] | None,
        "A list of properties to include in the search. Options: `title`, `description`, `html`. By default, all properties are searched.",  # noqa: E501
    ] = None,
    search_term: Annotated[
        str | None, "The term to search for in the HubSpot website content."
    ] = None,
    show_autocomplete: Annotated[
        bool | None, "Specify whether autocomplete results should be shown. Defaults to false."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/site-search/search_search'."
]:
    """Search for website content on a HubSpot account.

    Use this tool to retrieve website content that matches specified search criteria within a HubSpot account. Filters can include content type, domain, or URL path."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/site-search/search",
        method="GET",
        params=remove_none_values({
            "q": search_term,
            "limit": results_limit,
            "offset": pagination_offset,
            "language": content_language_code,
            "matchPrefix": invert_path_prefix_filter,
            "autocomplete": show_autocomplete,
            "popularityBoost": popularity_boost,
            "boostLimit": maximum_boost_limit,
            "boostRecent": boost_recent_time_window,
            "tableId": hubdb_table_id,
            "hubdbQuery": hubdb_query_filter,
            "domain": search_domains,
            "type": content_type_filters,
            "pathPrefix": path_prefixes,
            "property": search_properties,
            "length": result_length,
            "groupId": blog_ids_to_search,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_file_metadata(
    context: ToolContext,
    file_environment: Annotated[
        str, "Specify the environment of the file, either 'draft' or 'published'."
    ],
    file_path: Annotated[str, "The file system location of the file to retrieve metadata for."],
    include_properties: Annotated[
        str | None,
        "Comma-separated list of additional properties to include in the metadata response, if applicable.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/source-code/{environment}/metadata/{path}_get'.",
]:
    """Retrieve metadata for a file in a specified environment.

    Use this tool to obtain the metadata object for a file located at a specified path within a designated environment in the HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/source-code/{environment}/metadata/{path}".format(  # noqa: UP032
            environment=file_environment, path=file_path
        ),
        method="GET",
        params=remove_none_values({"properties": include_properties}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def download_file_from_hubspot_cms(
    context: ToolContext,
    file_environment: Annotated[
        str, "Specify the environment of the file. Options are 'draft' or 'published'."
    ],
    file_system_path: Annotated[
        str, "The file system path to the file within HubSpot CMS to be downloaded."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/source-code/{environment}/content/{path}_download'.",  # noqa: E501
]:
    """Download file content from HubSpot CMS by path and environment.

    Use this tool to download the byte contents of a file stored in HubSpot CMS. Specify the environment and path to retrieve the desired file content."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/source-code/{environment}/content/{path}".format(  # noqa: UP032
            environment=file_environment, path=file_system_path
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_file_in_cms_environment(
    context: ToolContext,
    file_environment: Annotated[
        str,
        'Specify the environment of the file to delete. Valid values are "draft" or "published".',
    ],
    file_system_location: Annotated[str, "The file system location of the file to be deleted."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/source-code/{environment}/content/{path}_archive'.",  # noqa: E501
]:
    """Deletes a file in a specified CMS environment.

    Use this tool to delete a file located at a specific path within a designated CMS environment. It's useful for managing and organizing source code by removing unnecessary or outdated files."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/source-code/{environment}/content/{path}".format(  # noqa: UP032
            environment=file_environment, path=file_system_location
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def extract_zip_file_async(
    context: ToolContext,
    zip_file_path: Annotated[
        str, "The path to the zip file in the developer file system that needs extraction."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/source-code/extract/async_doAsync'.",
]:
    """Initiate asynchronous extraction of a zip file on HubSpot CMS.

    This tool initiates the extraction of a zip file in the developer file system asynchronously. This is useful for handling large files or files that need processing without blocking operations. Use this tool when you need to start a zip file extraction and check its status later through the appropriate status endpoint."""  # noqa: E501
    request_data: Any = {"path": zip_file_path}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/source-code/extract/async",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def check_extraction_status(
    context: ToolContext,
    extraction_task_id: Annotated[
        int,
        "The unique ID of the extraction task, obtained from the initial `extract/async` request. This ID is required to check the status of the extraction.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/source-code/extract/async/tasks/{taskId}/status_getAsyncStatus'.",  # noqa: E501
]:
    """Retrieve the current status of a source-code extraction task.

    Use this tool to check the status of a specific source-code extraction by providing the `taskId`. It's useful for tracking the progress of an extraction after initiating it with the `extract/async` request."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/source-code/extract/async/tasks/{taskId}/status".format(  # noqa: UP032
            taskId=extraction_task_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_tags(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    update_deleted_blog_tags: Annotated[
        bool | None,
        "Specifies whether to update deleted blog tags. Defaults to false.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/batch/update_updateBatch'.",
]:
    """Update multiple blog tags in HubSpot CMS.

    Use this tool to update blog tag objects identified in the request body within the HubSpot CMS. It is useful for bulk modifications of blog tags.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires query parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required query parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required query parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/tags/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGS"],
        params=remove_none_values({"archived": update_deleted_blog_tags}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_tag_languages(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/multi-language/update-languages_updateLangs'.",  # noqa: E501
]:
    """Update languages for blog tags in a multi-language group.

    Use this tool to set new languages for each blog tag within a multi-language group in the HubSpot CMS.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGLANGUAGES"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGLANGUAGES"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGLANGUAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/tags/multi-language/update-languages",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBLOGTAGLANGUAGES"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def detach_blog_tag_from_language_group(
    context: ToolContext,
    blog_tag_id: Annotated[
        str, "ID of the blog tag to remove from a multi-language group in HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/multi-language/detach-from-lang-group_detachFromLangGroup'.",  # noqa: E501
]:
    """Detach a Blog Tag from a multi-language group.

    Use this tool to remove a blog tag from its association with a multi-language group in HubSpot CMS. This can be useful when restructuring tags or changing their multi-language settings."""  # noqa: E501
    request_data: Any = {"id": blog_tag_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/multi-language/detach-from-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_tags_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/batch/create_createBatch'.",
]:
    """Create multiple blog tags in a single request.

    This tool is used to create multiple blog tag objects as specified in the request body. Call this tool when you need to add several blog tags at once to a HubSpot CMS blog.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBLOGTAGSBATCH"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGTAGSBATCH"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["CREATEBLOGTAGSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/blogs/tags/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBLOGTAGSBATCH"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def archive_blog_tags(
    context: ToolContext,
    blog_tag_identifiers: Annotated[
        list[str], "An array of strings representing the blog tag identifiers to be archived."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/batch/archive_archiveBatch'.",
]:
    """Archive multiple blog tags in HubSpot CMS.

    Use this tool to delete multiple blog tag objects in HubSpot CMS by providing their identifiers. Suitable for cleaning up unused or obsolete tags in batches."""  # noqa: E501
    request_data: Any = {"inputs": blog_tag_identifiers}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/batch/archive",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def set_blog_tag_primary_language(
    context: ToolContext,
    primary_language_tag_id: Annotated[
        str, "ID of the blog tag to set as the primary language in a multi-language group."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/cms/v3/blogs/tags/multi-language/set-new-lang-primary_setLangPrimary'.",  # noqa: E501
]:
    """Set a blog tag as the primary language in a multi-language group.

    This tool updates a blog tag to be the primary language within a multi-language group, ensuring content consistency across different languages."""  # noqa: E501
    request_data: Any = {"id": primary_language_tag_id}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/multi-language/set-new-lang-primary",
        method="PUT",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_tags(
    context: ToolContext,
    blog_tag_ids: Annotated[
        list[str], "A list of strings representing the IDs of the Blog Tag objects to retrieve."
    ],
    include_deleted_tags: Annotated[
        bool | None,
        "Boolean indicating if deleted Blog Tags should be included in the response. If true, deleted tags are returned.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/tags/batch/read_readBatch'."
]:
    """Retrieve Blog Tag objects based on specified IDs.

    This tool retrieves detailed information about specific Blog Tag objects in HubSpot CMS, identified by the IDs provided in the request."""  # noqa: E501
    request_data: Any = {"inputs": blog_tag_ids}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/batch/read",
        method="POST",
        params=remove_none_values({"archived": include_deleted_tags}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_blog_tags(
    context: ToolContext,
    additional_properties: Annotated[
        str | None,
        "Include additional fields in the response. Specify the properties you want to retrieve.",
    ] = None,
    created_after_time: Annotated[
        str | None,
        "Only return Blog Tags created after the specified time. Provide the time in ISO 8601 format.",  # noqa: E501
    ] = None,
    filter_by_creation_time: Annotated[
        str | None,
        "Filter blog tags to return only those created at the specified exact time. Use ISO 8601 format for the date-time.",  # noqa: E501
    ] = None,
    filter_tags_created_before: Annotated[
        str | None,
        "Only return blog tags created before the specified date and time in UTC, formatted as an ISO 8601 string.",  # noqa: E501
    ] = None,
    include_archived_tags: Annotated[
        bool | None, "Specify whether to return deleted blog tags. Defaults to false."
    ] = None,
    last_updated_before: Annotated[
        str | None, "Return blog tags updated before this date and time (in ISO 8601 format)."
    ] = None,
    maximum_results_to_return: Annotated[
        int | None, "Specify the maximum number of blog tags to return. Default is 100."
    ] = None,
    pagination_cursor_token: Annotated[
        str | None,
        "The token to retrieve the next set of results from a paginated response. Obtain this from the `paging.next.after` property in the previous response.",  # noqa: E501
    ] = None,
    sort_fields_for_results: Annotated[
        list[str] | None,
        "Fields to sort results by. Options: `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`. Default is `createdAt`.",  # noqa: E501
    ] = None,
    updated_after: Annotated[
        str | None,
        "Return Blog Tags last updated after the specified date and time. Provide in ISO 8601 format, e.g., '2023-10-21T15:30:00Z'.",  # noqa: E501
    ] = None,
    updated_at_exact: Annotated[
        str | None, "Return blog tags last updated at the specified exact time (ISO 8601 format)."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/tags_getPage'."]:
    """Retrieve a list of blog tags with paging and filtering options.

    This tool fetches the list of blog tags, supporting paging and filtering. It is useful for integrations that need to access blog tags for analysis or editing suggestions."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags",
        method="GET",
        params=remove_none_values({
            "createdAt": filter_by_creation_time,
            "createdAfter": created_after_time,
            "createdBefore": filter_tags_created_before,
            "updatedAt": updated_at_exact,
            "updatedAfter": updated_after,
            "updatedBefore": last_updated_before,
            "sort": sort_fields_for_results,
            "after": pagination_cursor_token,
            "limit": maximum_results_to_return,
            "archived": include_archived_tags,
            "property": additional_properties,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_tag(
    context: ToolContext,
    blog_tag_unique_id: Annotated[str, "The unique ID assigned to the Blog Tag."],
    creation_timestamp: Annotated[
        str,
        "The timestamp (ISO8601 format) when this Blog Tag was created. Leave empty if not applicable.",  # noqa: E501
    ],
    deletion_timestamp: Annotated[
        str, "The ISO8601 timestamp marking when the blog tag was deleted."
    ],
    language_code: Annotated[
        str,
        "The explicit ISO 639 language code for the tag, such as 'en' for English or 'es' for Spanish.",  # noqa: E501
    ],
    primary_tag_translation_id: Annotated[
        int, "Specify the ID of the primary tag that this tag was translated from."
    ],
    tag_name: Annotated[str, "The name for the new blog tag to be created in HubSpot CMS."],
    updated_timestamp: Annotated[
        str, "The timestamp (ISO8601 format) when the Blog Tag was last updated."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'post-/cms/v3/blogs/tags_create'."]:
    """Create a new blog tag in HubSpot CMS.

    This tool creates a new tag for a blog in the HubSpot CMS. It should be called when there's a need to categorize or organize content within the blog by creating new tags."""  # noqa: E501
    request_data: Any = {
        "deletedAt": deletion_timestamp,
        "created": creation_timestamp,
        "name": tag_name,
        "language": language_code,
        "id": blog_tag_unique_id,
        "translatedFromId": primary_tag_translation_id,
        "updated": updated_timestamp,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def attach_tag_to_language_group(
    context: ToolContext,
    designated_language: Annotated[
        str,
        "Specify the language of the blog tag to add to a multi-language group. Use a language code like 'en' for English.",  # noqa: E501
    ],
    object_id_for_multilanguage_group: Annotated[
        str, "ID of the blog tag to add to the multi-language group."
    ],
    primary_language_object_id: Annotated[
        str, "ID of the primary language object in the multi-language group."
    ],
    primary_language: Annotated[
        str | None, "Specifies the primary language of the multi-language group."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/multi-language/attach-to-lang-group_attachToLangGroup'.",  # noqa: E501
]:
    """Attach a blog tag to a multi-language group.

    Use this tool to connect a specific blog tag to a designated multi-language group within HubSpot CMS. This is useful for managing tags across different language versions of a blog."""  # noqa: E501
    request_data: Any = {
        "language": designated_language,
        "id": object_id_for_multilanguage_group,
        "primaryId": primary_language_object_id,
        "primaryLanguage": primary_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/multi-language/attach-to-lang-group",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def retrieve_blog_tag_by_id(
    context: ToolContext,
    blog_tag_id: Annotated[str, "The unique identifier for the Blog Tag to be retrieved."],
    include_archived_blog_tags: Annotated[
        bool | None,
        "Specify whether to include archived blog tags in the response. Defaults to `false`.",
    ] = None,
    property_name: Annotated[
        str | None,
        "Specify a property of the blog tag to retrieve. Leave blank to get all details.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/blogs/tags/{objectId}_getById'."
]:
    """Retrieve blog tag details using its ID.

    Use this tool to get information about a blog tag by providing its unique ID. Call this tool when you need details about a specific blog tag in the HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/{objectId}".format(objectId=blog_tag_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "archived": include_archived_blog_tags,
            "property": property_name,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_blog_tag(
    context: ToolContext,
    blog_tag_id: Annotated[str, "The unique identifier for the Blog Tag to be deleted."],
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only blog tags that have been archived."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/cms/v3/blogs/tags/{objectId}_archive'."
]:
    """Delete a specified Blog Tag in HubSpot CMS.

    Use this tool to delete a specific Blog Tag in the HubSpot CMS by providing the object's ID. This is useful for managing and organizing blog content within the CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/{objectId}".format(objectId=blog_tag_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({"archived": return_only_archived_results}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_blog_tag(
    context: ToolContext,
    blog_tag_id: Annotated[str, "The unique identifier for the blog tag to be updated."],
    blog_tag_unique_id: Annotated[str, "The unique ID of the Blog Tag to be updated."],
    created_timestamp: Annotated[
        str,
        "The timestamp (ISO8601 format) when the blog tag was created. This field is optional and can be used if the creation time needs to be updated.",  # noqa: E501
    ],
    deleted_timestamp: Annotated[
        str, "The ISO8601 timestamp indicating when the blog tag was deleted."
    ],
    language: Annotated[
        str, "The ISO 639 language code for the tag. Select from the available options."
    ],
    last_updated_timestamp: Annotated[
        str, "The ISO8601 timestamp when the blog tag was last updated."
    ],
    primary_tag_translated_from_id: Annotated[
        int, "ID of the primary tag from which this tag was translated."
    ],
    tag_name: Annotated[
        str,
        "The new name for the blog tag to be updated. This is a required field when changing the tag's name.",  # noqa: E501
    ],
    update_deleted_blog_tags: Annotated[
        bool | None, "Set to `true` to update deleted Blog Tags. Defaults to `false`."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'patch-/cms/v3/blogs/tags/{objectId}_update'."
]:
    """Update specific fields of a blog tag by its ID.

    This tool updates specific fields of a single blog tag identified by its object ID. It enables partial updates, allowing changes to only the specified fields."""  # noqa: E501
    request_data: Any = {
        "deletedAt": deleted_timestamp,
        "created": created_timestamp,
        "name": tag_name,
        "language": language,
        "id": blog_tag_unique_id,
        "translatedFromId": primary_tag_translated_from_id,
        "updated": last_updated_timestamp,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/{objectId}".format(objectId=blog_tag_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({"archived": update_deleted_blog_tags}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_blog_tag_language_variation(
    context: ToolContext,
    blog_tag_id: Annotated[
        str, "ID of the existing blog tag to be cloned for creating a language variation."
    ],
    new_blog_tag_name: Annotated[str, "The name for the newly cloned blog tag language variation."],
    primary_blog_tag_language: Annotated[
        str | None,
        "Specify the language code of the primary blog tag to be cloned, such as 'en', 'fr', etc.",
    ] = None,
    target_language_for_blog_tag_variation: Annotated[
        str | None, "Specifies the target language for the new blog tag variant."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/cms/v3/blogs/tags/multi-language/create-language-variation_createLangVariation'.",  # noqa: E501
]:
    """Create a new language variation from an existing blog tag.

    Use this tool to create a language variation for an existing blog tag on HubSpot CMS. Call this tool when you need to support multiple languages for blog tags."""  # noqa: E501
    request_data: Any = {
        "name": new_blog_tag_name,
        "language": target_language_for_blog_tag_variation,
        "id": blog_tag_id,
        "primaryLanguage": primary_blog_tag_language,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/blogs/tags/multi-language/create-language-variation",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def fetch_url_redirects(
    context: ToolContext,
    created_after_date: Annotated[
        str | None, "Return redirects created after this date (format: YYYY-MM-DD)."
    ] = None,
    created_at: Annotated[
        str | None, "Return redirects created exactly on this date. Format: YYYY-MM-DD."
    ] = None,
    filter_by_exact_update_date: Annotated[
        str | None,
        "Return redirects last updated on this exact date. Use a valid date format (e.g., YYYY-MM-DD).",  # noqa: E501
    ] = None,
    filter_created_before_date: Annotated[
        str | None, "Return redirects created before this date. Expected format: YYYY-MM-DD."
    ] = None,
    filter_updated_after: Annotated[
        str | None, "Only include redirects updated after this specified date (ISO 8601 format)."
    ] = None,
    paging_cursor_token: Annotated[
        str | None, "Token for the last read resource to fetch additional results."
    ] = None,
    results_per_page_limit: Annotated[
        int | None, "Maximum number of URL redirects to return per page. Use to control pagination."
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived URL redirects, false to exclude them."
    ] = None,
    sort_criteria: Annotated[
        list[str] | None,
        "Specify fields to sort the URL redirects by. Can include multiple fields such as 'createdAt' or 'updatedAt'.",  # noqa: E501
    ] = None,
    updated_before_date: Annotated[
        str | None, "Only return redirects last updated before this date (in YYYY-MM-DD format)."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/cms/v3/url-redirects/_getPage'."
]:
    """Retrieve all URL redirects with optional filters.

    Use this tool to access a list of all existing URL redirects in the HubSpot CMS. You can apply filters based on creation or updated date to narrow down the results."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/url-redirects/",
        method="GET",
        params=remove_none_values({
            "createdAt": created_at,
            "createdAfter": created_after_date,
            "createdBefore": filter_created_before_date,
            "updatedAt": filter_by_exact_update_date,
            "updatedAfter": filter_updated_after,
            "updatedBefore": updated_before_date,
            "sort": sort_criteria,
            "after": paging_cursor_token,
            "limit": results_per_page_limit,
            "archived": return_archived_only,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def create_url_redirect(
    context: ToolContext,
    redirect_destination_url: Annotated[
        str,
        "The target URL to which the redirect will point. This should be a valid and complete URL.",
    ],
    redirect_style: Annotated[
        int,
        "Integer indicating the style of the redirect. Defines the behavior and type of the redirect, such as permanent (301) or temporary (302).",  # noqa: E501
    ],
    route_prefix: Annotated[
        str,
        "Defines the prefix for the URL route to be redirected. Provide the specific path without the domain, e.g., '/example-path'.",  # noqa: E501
    ],
    apply_only_after_not_found: Annotated[
        bool | None, "Applies the redirect only after a 404 Not Found response if true."
    ] = None,
    enable_pattern_matching: Annotated[
        bool | None,
        "Set to true to enable URL pattern matching for the redirect. False matches exact URLs only.",  # noqa: E501
    ] = None,
    enable_protocol_agnostic: Annotated[
        bool | None,
        "Set to true to ignore the protocol (HTTP/HTTPS) when matching URLs. Enables protocol-agnostic redirects.",  # noqa: E501
    ] = None,
    ignore_trailing_slash: Annotated[
        bool | None, "Set to true to ignore trailing slashes, allowing flexibility in URL matches."
    ] = None,
    match_full_url: Annotated[
        bool | None, "Set to true to match the entire URL exactly. False applies partial matching."
    ] = None,
    match_query_string: Annotated[
        bool | None,
        "Indicates whether the redirect should match the query string. Set to true to match, false to ignore.",  # noqa: E501
    ] = None,
    redirect_precedence: Annotated[
        int | None,
        "An integer indicating the priority of the redirect if multiple rules match. Higher numbers take precedence.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/cms/v3/url-redirects/_create'."
]:
    """Creates and configures a new URL redirect in HubSpot CMS.

    Use this tool to create and set up a new URL redirect within the HubSpot CMS. Ideal for managing website redirects effectively."""  # noqa: E501
    request_data: Any = {
        "isTrailingSlashOptional": ignore_trailing_slash,
        "isMatchQueryString": match_query_string,
        "redirectStyle": redirect_style,
        "routePrefix": route_prefix,
        "isMatchFullUrl": match_full_url,
        "isProtocolAgnostic": enable_protocol_agnostic,
        "destination": redirect_destination_url,
        "isOnlyAfterNotFound": apply_only_after_not_found,
        "isPattern": enable_pattern_matching,
        "precedence": redirect_precedence,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/url-redirects/",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def get_url_redirect_details_by_id(
    context: ToolContext,
    url_redirect_id: Annotated[str, "The ID of the target URL redirect to fetch details for."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/cms/v3/url-redirects/{urlRedirectId}_getById'.",
]:
    """Retrieve details of a URL redirect by ID.

    Use this tool to obtain the details of a specific URL redirect by providing its ID. Ideal for managing URL redirects within HubSpot CMS."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/url-redirects/{urlRedirectId}".format(  # noqa: UP032
            urlRedirectId=url_redirect_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def delete_url_redirect(
    context: ToolContext,
    url_redirect_id: Annotated[
        str, "The unique identifier of the URL redirect to be deleted within the HubSpot CMS."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/cms/v3/url-redirects/{urlRedirectId}_archive'.",
]:
    """Deletes an existing URL redirect in HubSpot CMS.

    Utilize this tool to delete a specific URL redirect within the HubSpot CMS system. This should be called when you need to remove a redirect mapping from the system."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.hubapi.com/cms/v3/url-redirects/{urlRedirectId}".format(  # noqa: UP032
            urlRedirectId=url_redirect_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["content"]))
async def update_url_redirect_settings(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    url_redirect_id: Annotated[
        str | None,
        "The unique identifier for the URL redirect to be updated. This ID specifies which redirect's settings will be modified.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/cms/v3/url-redirects/{urlRedirectId}_update'.",
]:
    """Update settings for an existing URL redirect in HubSpot CMS.

    This tool updates the configuration of a specified URL redirect in HubSpot CMS. Use it when you need to change the destination or other settings of an existing URL redirect.

    Note: Understanding the request schema is necessary to properly create
    the stringified JSON input object for execution.\n\nThis operation also requires path parameters.

    Modes:
    - GET_REQUEST_SCHEMA: Returns the schema. Only call if you don't
      already have it. Do NOT call repeatedly if you already received
      the schema.
    - EXECUTE: Performs the operation with the provided request body
      JSON.\n      Note: You must also provide the required path parameters when executing.

    If you need the schema, call with mode='get_request_schema' ONCE, then execute.
    """  # noqa: E501
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["UPDATEURLREDIRECTSETTINGS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated the object following the schema "
                "structure and requirements, call this tool again with "
                "mode='execute' and the stringified JSON as the "
                "request_body parameter along with the required path parameters. "
                "Do NOT call the schema mode again - you already have "
                "the schema now."
            ),
        }

    # Mode is EXECUTE - validate parameters
    # Validate required parameters
    missing_params = []
    if not url_redirect_id:
        missing_params.append(("url_redirect_id", "path"))

    if missing_params:
        param_names = [p[0] for p in missing_params]
        param_details = ", ".join([f"{p[0]} ({p[1]})" for p in missing_params])
        raise RetryableToolError(
            message=f"Missing required parameters: {param_names}",
            developer_message=(f"Required parameters validation failed: {param_details}"),
            additional_prompt_content=(
                f"The following required parameters are missing: "
                f"{param_details}. Please call this tool again with all "
                "required parameters."
            ),
        )

    # Validate request body is provided (not None or empty string)
    # Note: Empty objects like {} are allowed - schema validation will check if valid
    if request_body is None or request_body.strip() == "":
        raise RetryableToolError(
            message="Request body is required when mode is 'execute'",
            developer_message="The request_body parameter was null or empty string",
            additional_prompt_content=(
                "The request body is required to perform this operation. "
                "Use the schema below to construct a valid JSON object, "
                "then call this tool again in execute mode with the "
                "stringified JSON as the request_body parameter.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEURLREDIRECTSETTINGS"] + "\n```"
            ),
        )

    # Parse JSON
    try:
        request_data = json.loads(request_body)
    except json.JSONDecodeError as e:
        raise RetryableToolError(
            message=f"Invalid JSON in request body: {e!s}",
            developer_message=f"JSON parsing failed: {e!s}",
            additional_prompt_content=(
                f"The request body contains invalid JSON. Error: {e!s}\n\n"
                "Please provide a valid JSON string that matches the schema "
                "below, then call this tool again in execute mode.\n\n"
                "Schema:\n\n```json\n" + REQUEST_BODY_SCHEMAS["UPDATEURLREDIRECTSETTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/cms/v3/url-redirects/{urlRedirectId}".format(  # noqa: UP032
            urlRedirectId=url_redirect_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEURLREDIRECTSETTINGS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_auth_token_or_empty()
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}
