"""Arcade Starter Tools for hubspot_crm

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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def retrieve_hubspot_crm_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "The type of CRM object to retrieve (e.g., contacts, companies).  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    return_only_archived_records: Annotated[
        bool | None,
        "Set true to return only archived records; false to return unarchived records.  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-/crm/objects/v3/{objectType}/batch/read'."
]:
    """Retrieve HubSpot CRM records by ID or unique property.

    Use this tool to fetch HubSpot CRM records by specifying a record ID or a custom unique value property using the `idProperty` parameter.

    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["RETRIEVEHUBSPOTCRMRECORDS"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["RETRIEVEHUBSPOTCRMRECORDS"] + "\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["RETRIEVEHUBSPOTCRMRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/batch/read".format(  # noqa: UP032
            objectType=crm_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTCRMRECORDS"],
        params=remove_none_values({"archived": return_only_archived_records}),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_object_association(
    context: ToolContext,
    association_type: Annotated[
        str,
        "Specifies the type of association to create between the objects, such as 'contact_to_company'.",  # noqa: E501
    ],
    source_object_id: Annotated[
        str,
        "The ID of the primary object to associate in HubSpot. This should be a valid string representing the CRM object's unique identifier.",  # noqa: E501
    ],
    source_object_type: Annotated[
        str,
        "Type of the source object. Specify the CRM object type, such as 'contact', 'company', or 'deal'.",  # noqa: E501
    ],
    target_object_id: Annotated[
        str,
        "The ID of the target object to associate with. This is the object you want to link to the main object in HubSpot CRM.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of the target object to associate. Examples include 'contact', 'company', or 'deal'.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Create an association between two CRM objects in HubSpot.

    Use this tool to link two objects in HubSpot CRM by specifying their types, IDs, and the type of association. This is useful for connecting related records, such as associating a contact with a specific company or deal."""  # 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/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            objectType=source_object_type,
            objectId=source_object_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        method="PUT",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def remove_crm_association(
    context: ToolContext,
    association_type: Annotated[
        str,
        "The type of association between the CRM objects to be removed. Specify the nature of the relationship, such as 'contact-to-company'.",  # noqa: E501
    ],
    object_type: Annotated[
        str, "Specifies the type of the primary CRM object (e.g., 'contact', 'company')."
    ],
    source_object_id: Annotated[
        str, "The unique identifier of the source object whose association is to be removed."
    ],
    target_object_id: Annotated[
        str,
        "The ID of the target object to unlink from the source object. This must be a string representing the unique identifier.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "Specifies the type of the target CRM object to unlink. Examples include 'contact', 'company', etc.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Remove associations between CRM objects.

    This tool removes the association between two CRM objects based on specified types and IDs. Use it when you need to delete a relationship or link between specific CRM entities."""  # 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/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            objectType=object_type,
            objectId=source_object_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_multiple_hubspot_appointments(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubspot_object_type: Annotated[
        str | None,
        "Specify the type of HubSpot CRM object to update, e.g., 'appointments'.  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-/crm/objects/v3/{objectType}/batch/update'.",
]:
    """Update multiple appointments in HubSpot CRM.

    Use this tool to update multiple appointments by their internal IDs or unique property values within HubSpot CRM.

    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["UPDATEMULTIPLEHUBSPOTAPPOINTMENTS"],
            "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 hubspot_object_type:
        missing_params.append(("hubspot_object_type", "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["UPDATEMULTIPLEHUBSPOTAPPOINTMENTS"]
                + "\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["UPDATEMULTIPLEHUBSPOTAPPOINTMENTS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/batch/update".format(  # noqa: UP032
            objectType=hubspot_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLEHUBSPOTAPPOINTMENTS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_appointments_data(
    context: ToolContext,
    appointment_object_type: Annotated[
        str, "The type of HubSpot object to be queried, specifically for appointments."
    ],
    appointment_properties_to_return: Annotated[
        list[str] | None,
        "A list of property names to include in the response. Properties not present on the requested objects are ignored.",  # noqa: E501
    ] = None,
    only_archived_results: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    paging_cursor_token: Annotated[
        str | None, "Token indicating the last successfully read resource to continue pagination."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List properties to return with their history of values. Reduces max results per request.",
    ] = None,
    results_limit: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    retrieve_associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If specified associations don't exist, they will be ignored.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/objects/v3/{objectType}'."
]:
    """Retrieve a page of appointments from HubSpot CRM.

    Fetches appointment details using the specified properties to control the data returned. Useful for accessing and managing CRM appointment information."""  # 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/crm/objects/v3/{objectType}".format(  # noqa: UP032
            objectType=appointment_object_type
        ),
        method="GET",
        params=remove_none_values({
            "limit": results_limit,
            "after": paging_cursor_token,
            "properties": appointment_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_types,
            "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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_crm_appointment(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    appointment_object_type: Annotated[
        str | None,
        "Specifies the type of CRM object to create. For appointments, this should be 'appointment'.  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-/crm/objects/v3/{objectType}'."
]:
    """Create an appointment in the CRM with specified properties.

    Use this tool to schedule a new appointment within the CRM system. It requires the necessary properties for the appointment and returns the created appointment object along with 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 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["CREATECRMAPPOINTMENT"],
            "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 appointment_object_type:
        missing_params.append(("appointment_object_type", "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["CREATECRMAPPOINTMENT"] + "\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["CREATECRMAPPOINTMENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}".format(  # noqa: UP032
            objectType=appointment_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECRMAPPOINTMENT"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_batch_appointments(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    appointment_object_type: Annotated[
        str | None,
        "Specify the type of CRM object for the appointments, typically 'appointments'.  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-/crm/objects/v3/{objectType}/batch/create'.",
]:
    """Create multiple appointments in one request.

    This tool facilitates the creation of multiple appointments in a single API call. Use it when you need to schedule several appointments efficiently without making multiple requests.

    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["CREATEBATCHAPPOINTMENTS"],
            "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 appointment_object_type:
        missing_params.append(("appointment_object_type", "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["CREATEBATCHAPPOINTMENTS"] + "\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["CREATEBATCHAPPOINTMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/batch/create".format(  # noqa: UP032
            objectType=appointment_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHAPPOINTMENTS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_associations_in_hubspot(
    context: ToolContext,
    hubspot_object_id: Annotated[
        str,
        "The unique identifier for the HubSpot CRM object whose associations are being requested.",
    ],
    object_type: Annotated[
        str,
        "Specifies the type of HubSpot CRM object (e.g., contact, deal, company) whose associations you want to retrieve.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of the target object to which the association is being found. Specify the object type like 'contact', 'deal', 'company', etc.",  # noqa: E501
    ],
    include_full_associations: Annotated[
        bool | None,
        "Set to true to include full associations in the response, otherwise only basic associations will be returned.",  # noqa: E501
    ] = False,
    max_results: Annotated[
        int | None,
        "Specifies the maximum number of associations to return. Provide an integer value to limit the results.",  # noqa: E501
    ] = 500,
    paging_offset_after: Annotated[
        str | None,
        "A string used for pagination to get the next set of results after the specified cursor. Leave empty or omit for the first set of results.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}'.",  # noqa: E501
]:
    """Retrieve associations between HubSpot CRM objects.

    This tool is used to get the associations between specified HubSpot CRM objects. Useful for identifying relationships between different objects in the CRM, such as contacts, deals, or companies."""  # 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/crm/objects/v3/{objectType}/{objectId}/associations/{toObjectType}".format(  # noqa: UP032
            objectType=object_type, objectId=hubspot_object_id, toObjectType=target_object_type
        ),
        method="GET",
        params=remove_none_values({
            "after": paging_offset_after,
            "limit": max_results,
            "includeFA": include_full_associations,
        }),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def upsert_hubspot_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "The type of object in HubSpot CRM (e.g., contacts, companies) to create or update. Specify the object type relevant to your operation.  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-/crm/objects/v3/{objectType}/batch/upsert'.",
]:
    """Create or update HubSpot CRM records in batch mode.

    Use this tool to create or update records in HubSpot CRM by specifying a unique property value. The `idProperty` parameter helps identify records uniquely for batch operations.

    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["UPSERTHUBSPOTRECORDS"],
            "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 object_type:
        missing_params.append(("object_type", "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["UPSERTHUBSPOTRECORDS"] + "\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["UPSERTHUBSPOTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/batch/upsert".format(  # noqa: UP032
            objectType=object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTRECORDS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def search_appointments(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    appointment_search_criteria_type: Annotated[
        str | None,
        "Specify the type of object for the appointment search, such as 'appointments'. This determines the domain within the CRM to be searched.  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-/crm/objects/v3/{objectType}/search'."
]:
    """Search for appointments based on specified criteria.

    Use this tool to search for appointments in the CRM by specifying various criteria. It is useful for finding specific appointments or lists based on search conditions.

    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["SEARCHAPPOINTMENTS"],
            "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 appointment_search_criteria_type:
        missing_params.append(("appointment_search_criteria_type", "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["SEARCHAPPOINTMENTS"] + "\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["SEARCHAPPOINTMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/search".format(  # noqa: UP032
            objectType=appointment_search_criteria_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHAPPOINTMENTS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def archive_multiple_appointments(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    appointment_object_type: Annotated[
        str | None,
        "The type of object to be archived, typically 'appointments' for this endpoint.  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-/crm/objects/v3/{objectType}/batch/archive'.",
]:
    """Archive multiple appointments using their IDs.



    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["ARCHIVEMULTIPLEAPPOINTMENTS"],
            "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 appointment_object_type:
        missing_params.append(("appointment_object_type", "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["ARCHIVEMULTIPLEAPPOINTMENTS"]
                + "\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["ARCHIVEMULTIPLEAPPOINTMENTS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/batch/archive".format(  # noqa: UP032
            objectType=appointment_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEMULTIPLEAPPOINTMENTS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def gdpr_delete_object(
    context: ToolContext,
    object_id: Annotated[
        str, "The unique identifier for the CRM object to be deleted under GDPR compliance."
    ],
    object_type_for_gdpr_deletion: Annotated[
        str,
        "Specify the type of CRM object to delete (e.g., contacts, companies) for GDPR compliance.",
    ],
    unique_property_name: Annotated[
        str | None, "Specify a unique property name for the object to be deleted under GDPR."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/crm/objects/v3/{objectType}/gdpr-delete'.",
]:
    """Delete CRM objects in compliance with GDPR.

    This tool removes specified CRM objects in accordance with GDPR regulations. It's used when you need to ensure data is deleted to comply with GDPR requests."""  # noqa: E501
    request_data: Any = {"idProperty": unique_property_name, "objectId": 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/crm/objects/v3/{objectType}/gdpr-delete".format(  # noqa: UP032
            objectType=object_type_for_gdpr_deletion
        ),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def retrieve_crm_object(
    context: ToolContext,
    object_id: Annotated[
        str,
        "The ID of the CRM object to retrieve. This can be the internal object ID or a unique property value specified by the id_property.",  # noqa: E501
    ],
    object_type: Annotated[
        str,
        'Specifies the type of CRM object to retrieve, such as "contacts", "companies", or "deals".',  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for. Missing associations are ignored.",  # noqa: E501
    ] = None,
    properties_list: Annotated[
        list[str] | None,
        "An array of property names to be returned in the response. If any specified properties are not present, they will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List properties to be returned with their historical values. If a property doesn't exist, it'll be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of a property with unique values for this object to identify it instead of the default ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/objects/v3/{objectType}/{objectId}'."
]:
    """Fetch CRM object details by ID or unique property.

    Use this tool to retrieve details of a CRM object by specifying the object type and ID. You can control the properties returned using query parameters."""  # 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/crm/objects/v3/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=object_type, objectId=object_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": properties_list,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived_results,
            "idProperty": unique_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def delete_crm_object(
    context: ToolContext,
    crm_object_id: Annotated[
        str,
        "The unique identifier for the CRM object to be deleted. This ID specifies which object will be moved to the recycling bin.",  # noqa: E501
    ],
    crm_object_type: Annotated[
        str, "Specify the type of CRM object to delete, such as 'contact', 'deal', or 'company'."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/objects/v3/{objectType}/{objectId}'.",
]:
    """Delete a CRM object and move it to the recycling bin.

    Use this tool to delete an object in HubSpot CRM by specifying the object type and ID. The object will be moved to the recycling bin."""  # 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/crm/objects/v3/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=crm_object_type, objectId=crm_object_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_hubspot_object(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "The type of CRM object to update (e.g., contacts, companies).  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    object_identifier: Annotated[
        str | None,
        "A string representing the internal object ID or unique property value used to identify the HubSpot CRM object for updating.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property whose values are unique for the object, used to identify the object for the update.  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-/crm/objects/v3/{objectType}/{objectId}'.",
]:
    """Update specific properties of a HubSpot CRM object.

    This tool performs a partial update of an object in HubSpot CRM, identified by the internal object ID or a unique property value. It overwrites provided property values, errors out on read-only or non-existent properties, and allows clearing values by passing an empty string.

    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["UPDATEHUBSPOTOBJECT"],
            "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 object_type:
        missing_params.append(("object_type", "path"))
    if not object_identifier:
        missing_params.append(("object_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["UPDATEHUBSPOTOBJECT"] + "\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["UPDATEHUBSPOTOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/objects/v3/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=object_type, objectId=object_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTOBJECT"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def merge_hubspot_crm_objects(
    context: ToolContext,
    hubspot_object_type: Annotated[
        str, "Specify the type of HubSpot CRM object to merge, such as contact, company, or deal."
    ],
    object_id_to_merge: Annotated[
        str,
        "The ID of the HubSpot CRM object to be merged into the primary object. This should be a string identifier.",  # noqa: E501
    ],
    primary_object_id: Annotated[
        str, "The ID of the primary HubSpot CRM object to retain post-merge."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/objects/v3/{objectType}/merge'."
]:
    """Merge two HubSpot CRM objects into a single entity.

    Use this tool to merge two CRM objects of a specified type within HubSpot. It combines duplicate records into a single object, streamlining the data management process."""  # noqa: E501
    request_data: Any = {
        "objectIdToMerge": object_id_to_merge,
        "primaryObjectId": primary_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/crm/objects/v3/{objectType}/merge".format(  # noqa: UP032
            objectType=hubspot_object_type
        ),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_batch_associations_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    from_object_type: Annotated[
        str | None,
        "The type of the source object for the association (e.g., 'contact', 'company').  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    target_object_type: Annotated[
        str | None,
        "The type of the object that the associations will point to (e.g., 'contacts', 'companies').  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-/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/create_create'.",  # noqa: E501
]:
    """Batch create associations between object types in HubSpot CRM.

    This tool is used to batch create associations between two specified object types in HubSpot CRM, such as associating multiple contacts with companies in one request.

    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["CREATEBATCHASSOCIATIONSHUBSPOT"],
            "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 from_object_type:
        missing_params.append(("from_object_type", "path"))
    if not target_object_type:
        missing_params.append(("target_object_type", "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["CREATEBATCHASSOCIATIONSHUBSPOT"]
                + "\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["CREATEBATCHASSOCIATIONSHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/create".format(  # noqa: UP032
            fromObjectType=from_object_type, toObjectType=target_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHASSOCIATIONSHUBSPOT"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def batch_read_associations(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    source_object_type: Annotated[
        str | None,
        "The CRM object type from which the associations originate, such as 'contacts' or 'deals'. Specify a valid CRM object type.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    destination_object_type: Annotated[
        str | None,
        "Specify the CRM object type to associate with, such as 'contacts' or 'companies'.  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-/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/read_read'.",  # noqa: E501
]:
    """Retrieve batch associations between CRM object types in HubSpot.

    This tool retrieves a batch of association data between specified CRM object types in HubSpot, such as contacts to companies. Use it to efficiently access multiple associations at once.

    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["BATCHREADASSOCIATIONS"],
            "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 source_object_type:
        missing_params.append(("source_object_type", "path"))
    if not destination_object_type:
        missing_params.append(("destination_object_type", "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["BATCHREADASSOCIATIONS"] + "\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["BATCHREADASSOCIATIONS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/read".format(  # noqa: UP032
            fromObjectType=source_object_type, toObjectType=destination_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHREADASSOCIATIONS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def hubspot_batch_archive_associations(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    source_object_type: Annotated[
        str | None,
        "The type of the source object for the associations to be archived (e.g., 'contacts', 'companies').  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    target_object_type: Annotated[
        str | None,
        "Specify the type of the object to which the association is directed, e.g., 'company', 'deal'.  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-/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/archive_archive'.",  # noqa: E501
]:
    """Batch archive associations in HubSpot CRM.

    This tool is used to archive multiple associations between specified object types in HubSpot CRM in a single batch operation. Call this tool when you need to efficiently remove associations between records, such as contacts and companies, or deals and tickets, in bulk.

    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["HUBSPOTBATCHARCHIVEASSOCIATIONS"],
            "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 source_object_type:
        missing_params.append(("source_object_type", "path"))
    if not target_object_type:
        missing_params.append(("target_object_type", "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["HUBSPOTBATCHARCHIVEASSOCIATIONS"]
                + "\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["HUBSPOTBATCHARCHIVEASSOCIATIONS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/associations/{fromObjectType}/{toObjectType}/batch/archive".format(  # noqa: UP032
            fromObjectType=source_object_type, toObjectType=target_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["HUBSPOTBATCHARCHIVEASSOCIATIONS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_association_types(
    context: ToolContext,
    source_object_type: Annotated[
        str,
        "Specifies the source object type in HubSpot CRM from which associations are retrieved (e.g., 'contact', 'deal').",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "Specify the type of the destination object to retrieve association types for. These are the related entities in HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/associations/{fromObjectType}/{toObjectType}/types_getAll'.",  # noqa: E501
]:
    """Get association types between two object types in HubSpot CRM.

    Use this tool to retrieve all association types between specified object types in HubSpot CRM. This is useful when needing to understand the relationships between different entities in the CRM 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/crm/v3/associations/{fromObjectType}/{toObjectType}/types".format(  # noqa: UP032
            fromObjectType=source_object_type, toObjectType=target_object_type
        ),
        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=["crm.objects.contacts.write"]))
async def create_batch_of_calls(
    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-/crm/v3/objects/calls/batch/create_create'.",
]:
    """Create a batch of calls with specified properties and associations.

    This tool is used to create multiple calls at once in HubSpot CRM, specifying properties for each call and defining associations with other CRM records as needed.

    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["CREATEBATCHOFCALLS"],
            "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["CREATEBATCHOFCALLS"] + "\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["CREATEBATCHOFCALLS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFCALLS"],
        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=["crm.objects.contacts.write"]))
async def upsert_call_records(
    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-/crm/v3/objects/calls/batch/upsert_upsert'.",
]:
    """Create or update call records in HubSpot CRM.

    This tool allows you to create or update call records in HubSpot CRM. It uses a unique property value specified by the `idProperty` query parameter to identify records that need to be updated or created. Use this tool when you need to batch upsert call records.

    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["UPSERTCALLRECORDS"],
            "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["UPSERTCALLRECORDS"] + "\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["UPSERTCALLRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCALLRECORDS"],
        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=["crm.objects.contacts.read"]))
async def retrieve_calls_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived: Annotated[
        bool | None,
        "Set to 'true' to return only archived calls, 'false' to exclude them.  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-/crm/v3/objects/calls/batch/read_read'."
]:
    """Retrieve a batch of calls by ID from HubSpot CRM.

    Use this tool to fetch details of multiple calls from HubSpot CRM by providing their IDs. It returns information about each call in the batch.

    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["RETRIEVECALLSBATCH"],
            "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["RETRIEVECALLSBATCH"] + "\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["RETRIEVECALLSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVECALLSBATCH"],
        params=remove_none_values({"archived": return_only_archived}),
        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=["crm.objects.contacts.write"]))
async def update_batch_calls(
    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-/crm/v3/objects/calls/batch/update_update'.",
]:
    """Update multiple calls in HubSpot CRM by ID.

    This tool updates a batch of call records in HubSpot CRM using their IDs. Use it to modify details of multiple calls efficiently.

    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["UPDATEBATCHCALLS"],
            "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["UPDATEBATCHCALLS"] + "\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["UPDATEBATCHCALLS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHCALLS"],
        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=["crm.objects.contacts.read"]))
async def get_calls_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "Specify object types to retrieve associated IDs for. Comma-separated list. Non-existing associations are ignored.",  # noqa: E501
    ] = None,
    call_properties: Annotated[
        list[str] | None,
        "A list of properties to include in the response, such as call date, duration, etc.",
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token from the last successfully read resource for retrieving the next page of results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with historical values. Note: Reduces max calls per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None, "The maximum number of call records to display per page."
    ] = 10,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived call records. False will include non-archived records.",  # noqa: E501
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/calls_getPage'."
]:
    """Retrieve a page of call records from HubSpot CRM.

    This tool is used to retrieve a page of call records from the HubSpot CRM. The response can be customized by specifying the desired call properties through the `properties` 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/crm/v3/objects/calls",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": call_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.contacts.write"]))
async def create_hubspot_call(
    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-/crm/v3/objects/calls_create'."
]:
    """Create a call in HubSpot with specified properties.

    Use this tool to create a call in HubSpot CRM with specified properties. It returns the newly created call details, including its ID.

    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["CREATEHUBSPOTCALL"],
            "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["CREATEHUBSPOTCALL"] + "\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["CREATEHUBSPOTCALL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTCALL"],
        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=["crm.objects.contacts.read"]))
async def get_call_details(
    context: ToolContext,
    call_identifier: Annotated[
        str,
        "The unique identifier for the call. This can be an internal object ID or a unique property value as specified.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Specify object types to retrieve associated IDs for. Use comma-separated values.",
    ] = None,
    only_archived_results: Annotated[
        bool | None, "Specify `True` to return only archived results, otherwise `False`."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return with history. Ignores non-existent properties.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return in the response. Ignored if not present on requested object.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "Unique property name used to identify the call object in HubSpot CRM."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/calls/{callId}_getById'."
]:
    """Retrieve details of a call using its ID in HubSpot CRM.

    This tool fetches the details of a call object in HubSpot CRM using the call ID. It allows optional control over which properties of the call are returned. Use this tool to retrieve specific information on call objects by their unique identification."""  # 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/crm/v3/objects/calls/{callId}".format(callId=call_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_archived_results,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def archive_call_in_hubspot(
    context: ToolContext,
    call_identifier: Annotated[
        str, "The unique identifier for the call to be archived in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/calls/{callId}_archive'.",
]:
    """Archive a call in HubSpot CRM by moving it to the recycle bin.

    Use this tool to move a specific call, identified by its `callId`, to the recycling bin in HubSpot CRM."""  # 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/crm/v3/objects/calls/{callId}".format(callId=call_identifier),  # noqa: UP032
        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=["crm.objects.contacts.write"]))
async def update_call_info(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    call_identifier: Annotated[
        str | None,
        "The identifier for the call object you wish to update. This can be the internal call ID or a unique value defined by the `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for identifying the call object, other than the default ID.  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-/crm/v3/objects/calls/{callId}_update'."
]:
    """Update details of a specific call record in the CRM.

    Use this tool to perform a partial update of a call object in the HubSpot CRM using its `{callId}` or a unique property. Only provided properties will be updated. Read-only and non-existent properties will cause errors.

    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["UPDATECALLINFO"],
            "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 call_identifier:
        missing_params.append(("call_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["UPDATECALLINFO"] + "\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["UPDATECALLINFO"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/{callId}".format(callId=call_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECALLINFO"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def archive_calls_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-/crm/v3/objects/calls/batch/archive_archive'.",
]:
    """Archive a batch of calls by their IDs.

    Use this tool to archive multiple calls in HubSpot CRM by providing their IDs. Archived calls can be restored within 90 days, but call recordings are permanently deleted. Refer to HubSpot's documentation for more details on restoring activities.

    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["ARCHIVECALLSBATCH"],
            "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["ARCHIVECALLSBATCH"] + "\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["ARCHIVECALLSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECALLSBATCH"],
        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=["crm.objects.contacts.read"]))
async def search_calls_hubspot(
    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-/crm/v3/objects/calls/search_doSearch'."
]:
    """Search and filter call records in HubSpot CRM.

    Use this tool to search for calls in HubSpot CRM by applying filters on properties, searching through associations, and sorting the results. Ideal for retrieving specific call records from your HubSpot CRM.

    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["SEARCHCALLSHUBSPOT"],
            "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["SEARCHCALLSHUBSPOT"] + "\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["SEARCHCALLSHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/calls/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCALLSHUBSPOT"],
        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=["crm.objects.carts.write"]))
async def archive_carts_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-/crm/v3/objects/carts/batch/archive'."
]:
    """Archive multiple carts by ID in a batch operation.

    Use this tool to archive a batch of shopping carts by their IDs in HubSpot CRM. This is useful for managing and organizing outdated or completed carts.

    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["ARCHIVECARTSBATCH"],
            "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["ARCHIVECARTSBATCH"] + "\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["ARCHIVECARTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECARTSBATCH"],
        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=["crm.objects.carts.write"]))
async def create_batch_of_carts(
    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-/crm/v3/objects/carts/batch/create'."
]:
    """Create a batch of carts efficiently in HubSpot CRM.

    Use this tool to create multiple shopping carts at once within HubSpot CRM. Ideal for managing cart data in bulk efficiently.

    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["CREATEBATCHOFCARTS"],
            "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["CREATEBATCHOFCARTS"] + "\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["CREATEBATCHOFCARTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFCARTS"],
        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=["crm.objects.carts.read"]))
async def retrieve_cart_details(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List object types to retrieve associated IDs for. Ignored if associations don't exist.",
    ] = None,
    cart_properties_to_return: Annotated[
        list[str] | None,
        "List of properties to include in the response for each cart. Ignored if missing on objects.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Maximum number of results to display per page when retrieving cart details."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token for retrieving the next set of results. Use the `paging.next.after` from the previous response for more results.",  # noqa: E501
    ] = None,
    properties_with_history_list: Annotated[
        list[str] | None,
        "List of properties to return with their history in the response. Reduces max number of carts per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/carts'."]:
    """Retrieve detailed information about shopping carts.

    Use this tool to get information about shopping carts, with options to specify which details to retrieve via query parameters."""  # 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/crm/v3/objects/carts",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": cart_properties_to_return,
            "propertiesWithHistory": properties_with_history_list,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.carts.write"]))
async def create_cart_hubspot_crm(
    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-/crm/v3/objects/carts'."]:
    """Create a cart and retrieve its details including ID.

    Use this tool to create a new cart in HubSpot CRM with specified properties. It returns a detailed cart object including its unique ID. Ideal for setting up new customer carts.

    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["CREATECARTHUBSPOTCRM"],
            "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["CREATECARTHUBSPOTCRM"] + "\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["CREATECARTHUBSPOTCRM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECARTHUBSPOTCRM"],
        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=["crm.objects.carts.read"]))
async def retrieve_cart_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Default is 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-/crm/v3/objects/carts/batch/read'."
]:
    """Retrieve cart records by record ID or custom property.

    Use this tool to fetch cart records from HubSpot CRM by specifying record IDs or using a custom unique value property. Ideal for accessing detailed cart information.

    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["RETRIEVECARTRECORDS"],
            "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["RETRIEVECARTRECORDS"] + "\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["RETRIEVECARTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVECARTRECORDS"],
        params=remove_none_values({"archived": only_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=["crm.objects.carts.write"]))
async def upsert_cart_records(
    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-/crm/v3/objects/carts/batch/upsert'."
]:
    """Create or update cart records in HubSpot CRM.

    Use this tool to create or update cart records based on a unique property value specified by the `idProperty`. Ideal for managing cart data in bulk within the HubSpot CRM.

    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["UPSERTCARTRECORDS"],
            "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["UPSERTCARTRECORDS"] + "\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["UPSERTCARTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCARTRECORDS"],
        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=["crm.objects.carts.write"]))
async def update_carts_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-/crm/v3/objects/carts/batch/update'."
]:
    """Update a batch of carts by internal ID or unique properties.

    Use this tool to update multiple carts in HubSpot CRM by their internal IDs or unique property values. Useful for synchronizing cart information in bulk.

    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["UPDATECARTSBATCH"],
            "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["UPDATECARTSBATCH"] + "\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["UPDATECARTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECARTSBATCH"],
        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=["crm.objects.carts.read"]))
async def search_hubspot_carts(
    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-/crm/v3/objects/carts/search'."
]:
    """Search for carts in HubSpot CRM.

    This tool performs a search query to retrieve cart information from HubSpot CRM. It can be used to find specific carts using various search parameters.

    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["SEARCHHUBSPOTCARTS"],
            "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["SEARCHHUBSPOTCARTS"] + "\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["SEARCHHUBSPOTCARTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTCARTS"],
        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=["crm.objects.carts.read"]))
async def get_cart_details(
    context: ToolContext,
    cart_identifier: Annotated[
        str,
        "The unique identifier for the cart. This can be the internal ID or a unique property value specified by the `idProperty` parameter.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for, such as 'deals' or 'contacts'.",
    ] = None,
    only_return_archived_results: Annotated[
        bool | None, "Set to `true` to return only archived results; `false` includes all."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to retrieve alongside their history of previous values, separated by commas.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "List of specific properties to retrieve for the cart. Ignored if properties don't exist.",
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify a property name with unique values for the cart object, if not using the default internal ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/carts/{cartId}'."
]:
    """Retrieve detailed information of a cart by ID.

    Use this tool to get detailed information about a specific cart in the HubSpot CRM by providing the cart ID. It allows for optional specification of unique property values and controlled return 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/crm/v3/objects/carts/{cartId}".format(cartId=cart_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived_results,
            "idProperty": unique_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=["crm.objects.carts.write"]))
async def delete_shopping_cart(
    context: ToolContext,
    cart_identifier: Annotated[
        str,
        "The unique identifier of the shopping cart to delete from HubSpot CRM. It should be a string.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/carts/{cartId}'."
]:
    """Delete a shopping cart from HubSpot CRM.

    Use this tool to move a shopping cart, identified by its `cartId`, to the recycling bin in HubSpot CRM."""  # 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/crm/v3/objects/carts/{cartId}".format(cartId=cart_identifier),  # noqa: UP032
        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=["crm.objects.carts.write"]))
async def update_cart_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    cart_identifier: Annotated[
        str | None,
        "The unique identifier of the cart to be updated. This is required to specify which cart's properties will be modified.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_identifier_property: Annotated[
        str | None,
        "The name of the property with unique values for this cart object to identify it.  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-/crm/v3/objects/carts/{cartId}'."
]:
    """Update specific properties of a cart in HubSpot CRM.

    This tool updates specified properties of a cart identified by `cartId` within HubSpot CRM. It's used to perform partial updates, where provided property values are overwritten, and any attempt to modify read-only or non-existent properties will result in an error. Properties can be cleared by passing an empty string.

    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["UPDATECARTPROPERTIES"],
            "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 cart_identifier:
        missing_params.append(("cart_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["UPDATECARTPROPERTIES"] + "\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["UPDATECARTPROPERTIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/carts/{cartId}".format(cartId=cart_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECARTPROPERTIES"],
        params=remove_none_values({"idProperty": unique_identifier_property}),
        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=["crm.objects.commercepayments.write"]))
async def upsert_commerce_payments(
    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-/crm/v3/objects/commerce_payments/batch/upsert'.",
]:
    """Create or update unique commerce payment records in HubSpot.

    Use this tool to create new commerce payment records or update existing ones in HubSpot CRM. The tool identifies records by a unique property value specified by the `idProperty` query parameter.

    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["UPSERTCOMMERCEPAYMENTS"],
            "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["UPSERTCOMMERCEPAYMENTS"] + "\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["UPSERTCOMMERCEPAYMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCOMMERCEPAYMENTS"],
        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=["crm.objects.commercepayments.write"]))
async def create_batch_commerce_payments(
    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-/crm/v3/objects/commerce_payments/batch/create'.",
]:
    """Create a batch of commerce payments in HubSpot CRM.

    This tool is used to create a batch of commerce payments in the HubSpot CRM system. It should be called when there's a need to process multiple payment entries 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["CREATEBATCHCOMMERCEPAYMENTS"],
            "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["CREATEBATCHCOMMERCEPAYMENTS"]
                + "\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["CREATEBATCHCOMMERCEPAYMENTS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHCOMMERCEPAYMENTS"],
        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=["crm.objects.commercepayments.read"]))
async def retrieve_commerce_payment_details(
    context: ToolContext,
    commerce_payment_id: Annotated[
        str,
        "The unique identifier for the commerce payment to retrieve details for. It corresponds to the internal object ID by default.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. If any specified associations do not exist, they are ignored.",  # noqa: E501
    ] = None,
    only_return_archived_results: Annotated[
        bool | None, "Specify `true` to return only archived results. Default is `false`."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to retrieve along with their historical values. Specify as comma-separated values.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "A list of property names to be included in the response. Irrelevant properties will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "The property name used as a unique identifier for the commerce payment object."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/commerce_payments/{commercePaymentId}'.",
]:
    """Retrieve details of a specific commerce payment using its ID.

    Use this tool to obtain detailed information about a commerce payment by specifying its unique ID. This can include any details specified by the `properties` query parameter, if required."""  # 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/crm/v3/objects/commerce_payments/{commercePaymentId}".format(  # noqa: UP032
            commercePaymentId=commerce_payment_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived_results,
            "idProperty": unique_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=["crm.objects.commercepayments.write"]))
async def delete_commerce_payment(
    context: ToolContext,
    commerce_payment_id: Annotated[
        str, "The unique identifier for the commerce payment to be moved to the recycling bin."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/commerce_payments/{commercePaymentId}'.",  # noqa: E501
]:
    """Delete a commerce payment from the CRM system.

    Use this tool to move a specified commerce payment to the recycling bin by providing the payment 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/crm/v3/objects/commerce_payments/{commercePaymentId}".format(  # noqa: UP032
            commercePaymentId=commerce_payment_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=["crm.objects.commercepayments.write"]))
async def update_commerce_payment(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    commerce_payment_id: Annotated[
        str | None,
        "The internal ID of the commerce payment to update. This ID identifies the specific payment object within the system.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for identifying the object. Use this if not using the default internal ID.  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-/crm/v3/objects/commerce_payments/{commercePaymentId}'.",
]:
    """Partially update a commerce payment by ID or unique property.

    Use this tool to perform a partial update of a commerce payment object identified by its internal ID or a unique property specified by `idProperty`. Ensure provided property values exist and are not read-only; this will overwrite them. To clear a value, pass an empty string.

    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["UPDATECOMMERCEPAYMENT"],
            "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 commerce_payment_id:
        missing_params.append(("commerce_payment_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["UPDATECOMMERCEPAYMENT"] + "\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["UPDATECOMMERCEPAYMENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/{commercePaymentId}".format(  # noqa: UP032
            commercePaymentId=commerce_payment_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECOMMERCEPAYMENT"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.commercepayments.write"]))
async def archive_commerce_payments_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-/crm/v3/objects/commerce_payments/batch/archive'.",
]:
    """Archive a batch of commerce payments by ID.

    Use this tool to archive multiple commerce payments at once by their IDs. Ideal for managing and organizing payment data efficiently by removing outdated or unnecessary records in batches.

    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["ARCHIVECOMMERCEPAYMENTSBATCH"],
            "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["ARCHIVECOMMERCEPAYMENTSBATCH"]
                + "\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["ARCHIVECOMMERCEPAYMENTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECOMMERCEPAYMENTSBATCH"],
        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=["crm.objects.commercepayments.write"]))
async def update_commerce_payments_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-/crm/v3/objects/commerce_payments/batch/update'.",
]:
    """Update a batch of commerce payments by internal ID or unique values.

    Use this tool to update multiple commerce payments in HubSpot CRM by providing their internal IDs or unique property values, allowing for efficient bulk modifications.

    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["UPDATECOMMERCEPAYMENTSBATCH"],
            "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["UPDATECOMMERCEPAYMENTSBATCH"]
                + "\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["UPDATECOMMERCEPAYMENTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECOMMERCEPAYMENTSBATCH"],
        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=["crm.objects.commercepayments.read"]))
async def retrieve_commerce_payment_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_results_only: Annotated[
        bool | None,
        "Return only archived commerce payment records if set to true.  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-/crm/v3/objects/commerce_payments/batch/read'.",
]:
    """Retrieve commerce payment records by ID or unique property.

    This tool is used to fetch commerce payment records from HubSpot CRM by specifying record IDs or using a custom unique property for retrieval.

    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["RETRIEVECOMMERCEPAYMENTRECORDS"],
            "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["RETRIEVECOMMERCEPAYMENTRECORDS"]
                + "\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["RETRIEVECOMMERCEPAYMENTRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVECOMMERCEPAYMENTRECORDS"],
        params=remove_none_values({"archived": return_archived_results_only}),
        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=["crm.objects.commercepayments.read"]))
async def search_commerce_payments(
    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-/crm/v3/objects/commerce_payments/search'.",
]:
    """Search for commerce payments in HubSpot CRM.

    Use this tool to search for commerce payments within the HubSpot CRM. It should be called when you need to retrieve payment information from the CRM database.

    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["SEARCHCOMMERCEPAYMENTS"],
            "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["SEARCHCOMMERCEPAYMENTS"] + "\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["SEARCHCOMMERCEPAYMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCOMMERCEPAYMENTS"],
        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=["crm.objects.commercepayments.read"]))
async def get_commerce_payments(
    context: ToolContext,
    associations_to_retrieve: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Non-existing associations are ignored.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token from the last successfully read resource. Used for paginating through results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to be returned with their historical values. Reduces the maximum payments per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    returned_properties: Annotated[
        list[str] | None,
        "List the properties to return in the response. Non-present properties are ignored.",
    ] = None,
    show_only_archived: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/commerce_payments'."
]:
    """Retrieve a page of commerce payments from HubSpot CRM.

    This tool allows you to retrieve a page of commerce payments from HubSpot CRM, with customizable properties."""  # 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/crm/v3/objects/commerce_payments",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": returned_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associations_to_retrieve,
            "archived": show_only_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=["crm.objects.commercepayments.write"]))
async def create_commerce_payment(
    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-/crm/v3/objects/commerce_payments'."
]:
    """Create a commerce payment and return its details.

    Use this tool to create a new commerce payment in the HubSpot CRM. It returns the details of the payment, including the unique ID.

    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["CREATECOMMERCEPAYMENT"],
            "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["CREATECOMMERCEPAYMENT"] + "\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["CREATECOMMERCEPAYMENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/commerce_payments",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECOMMERCEPAYMENT"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_subscription_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-/crm/v3/objects/subscriptions/batch/update'.",
]:
    """Update multiple subscriptions by ID or property values.

    This tool updates a batch of subscriptions using their internal IDs or unique property values in HubSpot CRM. Use it when you need to modify details for multiple subscriptions simultaneously.

    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["UPDATESUBSCRIPTIONBATCH"],
            "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["UPDATESUBSCRIPTIONBATCH"] + "\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["UPDATESUBSCRIPTIONBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESUBSCRIPTIONBATCH"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def archive_subscriptions_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-/crm/v3/objects/subscriptions/batch/archive'.",
]:
    """Archive a batch of subscriptions by ID in HubSpot CRM.

    Use this tool to archive multiple subscriptions at once by providing their IDs. This is useful for managing bulk subscription updates in HubSpot CRM.

    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["ARCHIVESUBSCRIPTIONSBATCH"],
            "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["ARCHIVESUBSCRIPTIONSBATCH"] + "\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["ARCHIVESUBSCRIPTIONSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVESUBSCRIPTIONSBATCH"],
        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=["crm.objects.subscriptions.read"]))
async def search_hubspot_subscriptions(
    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-/crm/v3/objects/subscriptions/search'."
]:
    """Search for subscriptions in HubSpot CRM.

    Use this tool to search and retrieve details of specific subscriptions from HubSpot CRM based on given criteria.

    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["SEARCHHUBSPOTSUBSCRIPTIONS"],
            "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["SEARCHHUBSPOTSUBSCRIPTIONS"]
                + "\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["SEARCHHUBSPOTSUBSCRIPTIONS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTSUBSCRIPTIONS"],
        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=["crm.objects.subscriptions.read"]))
async def get_subscription_data(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Ignored if not existing.",
    ] = None,
    max_results_per_page: Annotated[
        int | None, "The maximum number of subscription results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token indicating the last successfully read resource, used for pagination in subsequent requests.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of property names for retrieving their values and history. Reduces max subscriptions per request.",  # noqa: E501
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "A list of property names to be included in the response. If a specified property is not present in the requested objects, it will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None,
        "Set to true to return only archived results. Set to false to include active results.",
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/subscriptions'."
]:
    """Fetch a page of subscription data from HubSpot CRM.

    This tool retrieves a page of subscription data from HubSpot CRM. Use this tool to access subscription information, with options to control the returned data via query parameters."""  # 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/crm/v3/objects/subscriptions",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_subscription(
    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-/crm/v3/objects/subscriptions'."
]:
    """Create a new subscription in HubSpot CRM.

    This tool creates a subscription in HubSpot CRM using specified properties and returns a copy of the object with its ID included. Use it to add new subscriptions to the CRM 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["CREATESUBSCRIPTION"],
            "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["CREATESUBSCRIPTION"] + "\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["CREATESUBSCRIPTION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATESUBSCRIPTION"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def upsert_subscriptions_in_hubspot_crm(
    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-/crm/v3/objects/subscriptions/batch/upsert'.",
]:
    """Batch create or update subscription records in HubSpot CRM.

    Use this tool to create or update subscription records based on a unique property value in HubSpot CRM. It's ideal for ensuring your subscription data is up-to-date by specifying an `idProperty` for uniquely identifying existing records.

    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["UPSERTSUBSCRIPTIONSINHUBSPOTCRM"],
            "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["UPSERTSUBSCRIPTIONSINHUBSPOTCRM"]
                + "\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["UPSERTSUBSCRIPTIONSINHUBSPOTCRM"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTSUBSCRIPTIONSINHUBSPOTCRM"],
        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=["crm.objects.subscriptions.read"]))
async def retrieve_subscription_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_archived_records: Annotated[
        bool | None,
        "Set to true to return only archived results.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/subscriptions/batch/read'.",
]:
    """Retrieve subscription records by ID or unique property.

    Use this tool to fetch subscription records from HubSpot CRM by providing record IDs or a custom unique value property. Ideal for accessing specific subscription details quickly.

    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["RETRIEVESUBSCRIPTIONRECORDS"],
            "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["RETRIEVESUBSCRIPTIONRECORDS"]
                + "\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["RETRIEVESUBSCRIPTIONRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVESUBSCRIPTIONRECORDS"],
        params=remove_none_values({"archived": only_archived_records}),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_batch_subscriptions(
    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-/crm/v3/objects/subscriptions/batch/create'.",
]:
    """Create a batch of subscriptions in HubSpot CRM.

    This tool is used to create multiple subscriptions at once in HubSpot CRM. Call this tool when you need to add several subscriptions simultaneously.

    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["CREATEBATCHSUBSCRIPTIONS"],
            "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["CREATEBATCHSUBSCRIPTIONS"] + "\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["CREATEBATCHSUBSCRIPTIONS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHSUBSCRIPTIONS"],
        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=["crm.objects.subscriptions.read"]))
async def get_subscription_details(
    context: ToolContext,
    subscription_id: Annotated[
        str,
        "The unique identifier for the subscription object to be retrieved. This can be the internal object ID or a unique property value specified by the idProperty query param.",  # noqa: E501
    ],
    association_types_to_retrieve: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Non-existing associations will be ignored.",  # noqa: E501
    ] = None,
    only_archived_results: Annotated[
        bool | None, "Set to true to return only archived items, false to exclude them."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names whose values and history are to be retrieved for the subscription. Properties not present on the object will be ignored.",  # noqa: E501
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "List of properties to return in the response. Properties not present in the object will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The property name used to uniquely identify the subscription object instead of the default ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/subscriptions/{subscriptionId}'.",
]:
    """Retrieve details of a specific subscription by ID.

    This tool is used to fetch the details of a subscription object identified by its subscription ID. It allows control over what is returned using query parameters."""  # 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/crm/v3/objects/subscriptions/{subscriptionId}".format(  # noqa: UP032
            subscriptionId=subscription_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": association_types_to_retrieve,
            "archived": only_archived_results,
            "idProperty": unique_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def delete_subscription(
    context: ToolContext,
    subscription_id: Annotated[
        str,
        "The unique identifier of the subscription to be deleted. This moves the subscription to the recycling bin in HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/subscriptions/{subscriptionId}'.",
]:
    """Delete a specific subscription from HubSpot CRM.

    This tool deletes a subscription identified by `subscriptionId` from HubSpot CRM, moving it to the recycling bin. Use this when you need to remove a subscription object 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/crm/v3/objects/subscriptions/{subscriptionId}".format(  # noqa: UP032
            subscriptionId=subscription_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_subscription(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    subscription_id: Annotated[
        str | None,
        "The identifier for the subscription to update, typically the internal object ID. Specify this to target the right subscription.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property to identify the subscription object for updating.  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-/crm/v3/objects/subscriptions/{subscriptionId}'.",
]:
    """Update subscription details using provided property values.

    Use this tool to perform a partial update of a subscription object in HubSpot CRM. Identify the object by its internal ID or a unique property value using the `idProperty` query parameter. Overwrite properties by providing new values; clear them by passing an empty string. Errors occur for read-only or non-existent properties.

    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["UPDATESUBSCRIPTION"],
            "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 subscription_id:
        missing_params.append(("subscription_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["UPDATESUBSCRIPTION"] + "\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["UPDATESUBSCRIPTION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/subscriptions/{subscriptionId}".format(  # noqa: UP032
            subscriptionId=subscription_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESUBSCRIPTION"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def update_messages_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-/crm/v3/objects/communications/batch/update_update'.",
]:
    """Update a batch of messages in HubSpot CRM.

    Use this tool to update multiple messages at once in HubSpot CRM by specifying either the communication ID or a unique property value. Only provided property values will be updated, and non-existent properties or read-only fields will cause errors. Include empty strings to clear properties.

    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["UPDATEMESSAGESBATCH"],
            "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["UPDATEMESSAGESBATCH"] + "\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["UPDATEMESSAGESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMESSAGESBATCH"],
        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=["crm.objects.contacts.write"]))
async def delete_batch_messages(
    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-/crm/v3/objects/communications/batch/archive_archive'.",
]:
    """Delete a batch of messages by ID with restoration option.

    Delete multiple messages by their IDs. Deleted messages can be restored within 90 days.

    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.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["DELETEBATCHMESSAGES"],
            "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["DELETEBATCHMESSAGES"] + "\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["DELETEBATCHMESSAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["DELETEBATCHMESSAGES"],
        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=["crm.objects.contacts.write"]))
async def upsert_communications_records(
    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-/crm/v3/objects/communications/batch/upsert_upsert'.",
]:
    """Create or update communication records in bulk.

    This tool allows you to batch create or update communication records in HubSpot CRM based on a unique property value. Utilize this tool when you need to synchronize communication data and ensure records are accurately up-to-date.

    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["UPSERTCOMMUNICATIONSRECORDS"],
            "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["UPSERTCOMMUNICATIONSRECORDS"]
                + "\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["UPSERTCOMMUNICATIONSRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCOMMUNICATIONSRECORDS"],
        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=["crm.objects.contacts.read"]))
async def read_communications_page(
    context: ToolContext,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token for the next set of results to read from the previous request's `paging.next.after` JSON property.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history. Reduces the maximum number of communications per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None, "The maximum number of results to display per page."
    ] = 10,
    retrieve_associations: Annotated[
        list[str] | None,
        "Comma-separated list of object types to get associated IDs for. Ignored if the association doesn't exist.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results. False includes all results."
    ] = False,
    specified_properties: Annotated[
        list[str] | None,
        "List of communication properties to return in the response. Properties not present will be ignored.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/communications_getPage'."
]:
    """Retrieve a page of communications from HubSpot CRM.

    Use this tool to read a page of communications. You can control the information returned using query parameters for specific properties."""  # 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/crm/v3/objects/communications",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": specified_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associations,
            "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=["crm.objects.contacts.write"]))
async def create_hubspot_communication(
    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-/crm/v3/objects/communications_create'."
]:
    """Create a new communication entry in HubSpot CRM.

    This tool creates a communication in HubSpot CRM with specified properties and returns the created object, including its ID. Use this when you need to log or track communications within the CRM.

    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["CREATEHUBSPOTCOMMUNICATION"],
            "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["CREATEHUBSPOTCOMMUNICATION"]
                + "\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["CREATEHUBSPOTCOMMUNICATION"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTCOMMUNICATION"],
        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=["crm.objects.contacts.read"]))
async def get_communication_by_id(
    context: ToolContext,
    communication_identifier: Annotated[
        str, "Specify the unique ID or property value for the communication object to retrieve."
    ],
    properties_to_return: Annotated[
        list[str] | None,
        "Comma-separated list of properties to include in the response. Missing properties are ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List the properties whose history of values should be returned. Comma-separated values are expected.",  # noqa: E501
    ] = None,
    retrieve_associated_object_types: Annotated[
        list[str] | None,
        "A list of object types for retrieving associated IDs. Non-existing associations will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None,
        "Set to true to return only results that have been archived. Defaults to false to include non-archived results.",  # noqa: E501
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "The property name used to uniquely identify the communication object. Allows retrieval by non-default identifiers.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/communications/{communicationId}_getById'.",  # noqa: E501
]:
    """Retrieve details of a communication by its ID.

    Use this tool to obtain the details of a communication object by specifying its unique ID. The ID can be the internal object ID or any unique property value. You can control the returned data using query parameters."""  # 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/crm/v3/objects/communications/{communicationId}".format(  # noqa: UP032
            communicationId=communication_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_types,
            "archived": return_only_archived,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def archive_communication(
    context: ToolContext,
    communication_id: Annotated[
        str,
        "The unique identifier for the communication object to be archived. It must be a valid string representing an existing communication ID.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/communications/{communicationId}_archive'.",  # noqa: E501
]:
    """Archive a communication by its ID.

    Use this tool to move a communication object to the recycling bin by specifying its 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/crm/v3/objects/communications/{communicationId}".format(  # noqa: UP032
            communicationId=communication_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=["crm.objects.contacts.write"]))
async def update_communication_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    communication_id: Annotated[
        str | None,
        "The internal object ID of the communication. Used to identify which communication object to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The unique property name used to identify the communication object if not using `communicationId`. It must refer to a property with unique values for the object.  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-/crm/v3/objects/communications/{communicationId}_update'.",  # noqa: E501
]:
    """Update communication object details in HubSpot CRM.

    Perform a partial update of a HubSpot CRM communication object using its `communicationId` or a unique property value. This tool overwrites provided property values, with errors for read-only or non-existent properties.

    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["UPDATECOMMUNICATIONDETAILS"],
            "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 communication_id:
        missing_params.append(("communication_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["UPDATECOMMUNICATIONDETAILS"]
                + "\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["UPDATECOMMUNICATIONDETAILS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/{communicationId}".format(  # noqa: UP032
            communicationId=communication_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECOMMUNICATIONDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.read"]))
async def search_crm_messages(
    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-/crm/v3/objects/communications/search_doSearch'.",
]:
    """Search and filter CRM messages based on various criteria.

    Use this tool to search for messages in the CRM by applying filters on properties, checking associations, and sorting the results. Ideal for retrieving specific communications data within the HubSpot CRM.

    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["SEARCHCRMMESSAGES"],
            "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["SEARCHCRMMESSAGES"] + "\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["SEARCHCRMMESSAGES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCRMMESSAGES"],
        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=["crm.objects.contacts.read"]))
async def retrieve_batch_communications(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to True to return only archived results.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/communications/batch/read_read'.",
]:
    """Retrieve a batch of communication messages by ID.

    This tool retrieves a batch of communication messages from the HubSpot CRM using either the message ID or a unique property value. It should be called when you need to access multiple messages simultaneously.

    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["RETRIEVEBATCHCOMMUNICATIONS"],
            "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["RETRIEVEBATCHCOMMUNICATIONS"]
                + "\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["RETRIEVEBATCHCOMMUNICATIONS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEBATCHCOMMUNICATIONS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.contacts.write"]))
async def create_hubspot_messages_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-/crm/v3/objects/communications/batch/create_create'.",
]:
    """Create a batch of messages in HubSpot CRM.

    Use this tool to create multiple messages in HubSpot CRM simultaneously. Ideal for handling bulk message creation with optional properties and associations with other CRM records.

    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["CREATEHUBSPOTMESSAGESBATCH"],
            "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["CREATEHUBSPOTMESSAGESBATCH"]
                + "\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["CREATEHUBSPOTMESSAGESBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/communications/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTMESSAGESBATCH"],
        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=["crm.objects.companies.read"]))
async def retrieve_batch_companies_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived companies in the results.  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-/crm/v3/objects/companies/batch/read_read'.",
]:
    """Retrieve a batch of company records from HubSpot CRM.

    Use this tool to retrieve multiple companies from HubSpot CRM by specifying their IDs or unique properties. You can customize the returned data using the `properties` query parameter.

    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["RETRIEVEBATCHCOMPANIESHUBSPOT"],
            "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["RETRIEVEBATCHCOMPANIESHUBSPOT"]
                + "\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["RETRIEVEBATCHCOMPANIESHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEBATCHCOMPANIESHUBSPOT"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.companies.read"]))
async def retrieve_all_companies(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None, "The cursor token to fetch the next set of results in a paginated response."
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "List of properties to include in the response. Ignore if not present on the object.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history of previous values. This reduces max companies per request.",  # noqa: E501
    ] = None,
    results_per_page_limit: Annotated[
        int | None, "The maximum number of results to display per page."
    ] = 10,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results in the response."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/companies_getPage'."
]:
    """Retrieve all companies from HubSpot CRM.

    This tool retrieves all companies from the HubSpot CRM. It uses query parameters to control the information that gets returned, such as filtering or specifying fields. Useful for getting a comprehensive list of companies stored in the CRM."""  # 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/crm/v3/objects/companies",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page_limit,
            "after": paging_cursor_token,
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.companies.write"]))
async def create_company_hubspot(
    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-/crm/v3/objects/companies_create'."
]:
    """Create a new company in HubSpot CRM.

    Use this tool to create a single company in HubSpot CRM. It allows you to define property values for the company and specify associations with other CRM records. Ideal for organizing and managing company information efficiently.

    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["CREATECOMPANYHUBSPOT"],
            "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["CREATECOMPANYHUBSPOT"] + "\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["CREATECOMPANYHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECOMPANYHUBSPOT"],
        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=["crm.objects.companies.read"]))
async def search_companies_in_hubspot(
    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-/crm/v3/objects/companies/search_doSearch'.",
]:
    """Search for companies in HubSpot CRM using filters and sorting.

    This tool allows you to search for companies in HubSpot CRM by applying filters, associating searches, and sorting the results. It is useful for retrieving company information based on specific criteria quickly.

    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["SEARCHCOMPANIESINHUBSPOT"],
            "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["SEARCHCOMPANIESINHUBSPOT"] + "\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["SEARCHCOMPANIESINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCOMPANIESINHUBSPOT"],
        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=["crm.objects.companies.read"]))
async def retrieve_company_by_id(
    context: ToolContext,
    company_identifier: Annotated[
        str,
        "A unique identifier for the company, such as its ID or a unique property name, used to retrieve its details.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Ignores non-existent associations.",
    ] = None,
    company_properties_to_retrieve: Annotated[
        list[str] | None,
        "List the specific company properties to retrieve, separated by commas. Ignored if properties are unavailable.",  # noqa: E501
    ] = None,
    retrieve_properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history of previous values. Ignored if properties aren't present.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None,
        "Set to true to return only archived results. If false, non-archived results will be returned.",  # noqa: E501
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property to identify the company. Used instead of company ID.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/companies/{companyId}_getById'.",
]:
    """Retrieve detailed company information using its ID.

    Use this tool to get detailed information about a company by providing its ID or a unique property. You can specify which properties to retrieve."""  # 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/crm/v3/objects/companies/{companyId}".format(  # noqa: UP032
            companyId=company_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": company_properties_to_retrieve,
            "propertiesWithHistory": retrieve_properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived,
            "idProperty": unique_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=["crm.objects.companies.write"]))
async def delete_company(
    context: ToolContext,
    company_id: Annotated[
        str,
        "The unique identifier of the company to be deleted in HubSpot CRM. This ID is required to specify the company.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/companies/{companyId}_archive'.",
]:
    """Delete a company by ID in HubSpot CRM.

    Use this tool to delete a company by its ID in HubSpot CRM. Deleted companies can be restored within 90 days 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/crm/v3/objects/companies/{companyId}".format(  # noqa: UP032
            companyId=company_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=["crm.objects.companies.write"]))
async def update_hubspot_company(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    company_unique_identifier: Annotated[
        str | None,
        "The unique identifier for the company to be updated, either the companyId or a unique property value.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of the unique property used to identify the company.  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-/crm/v3/objects/companies/{companyId}_update'.",
]:
    """Update a company's details in HubSpot CRM using its ID.

    Use this tool to update a company's information in HubSpot CRM by specifying the `companyId` or a unique property. Only existing and writable properties can be updated. Properties can be cleared by passing an empty string.

    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["UPDATEHUBSPOTCOMPANY"],
            "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 company_unique_identifier:
        missing_params.append(("company_unique_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["UPDATEHUBSPOTCOMPANY"] + "\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["UPDATEHUBSPOTCOMPANY"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/{companyId}".format(  # noqa: UP032
            companyId=company_unique_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTCOMPANY"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.companies.write"]))
async def create_or_update_companies(
    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-/crm/v3/objects/companies/batch/upsert_upsert'.",
]:
    """Create or update companies in HubSpot CRM using a unique identifier.

    This tool creates or updates companies in the HubSpot CRM based on a unique identifier property. It should be called when you need to batch upsert company records identified by a unique property.

    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["CREATEORUPDATECOMPANIES"],
            "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["CREATEORUPDATECOMPANIES"] + "\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["CREATEORUPDATECOMPANIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATECOMPANIES"],
        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=["crm.objects.companies.write"]))
async def update_batch_of_companies(
    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-/crm/v3/objects/companies/batch/update_update'.",
]:
    """Update multiple company records in HubSpot by ID.

    This tool allows batch updating of company records in the HubSpot CRM. It should be called when there is a need to update information for multiple companies at once, using their unique IDs.

    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["UPDATEBATCHOFCOMPANIES"],
            "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["UPDATEBATCHOFCOMPANIES"] + "\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["UPDATEBATCHOFCOMPANIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHOFCOMPANIES"],
        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=["crm.objects.companies.write"]))
async def create_batch_of_companies(
    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-/crm/v3/objects/companies/batch/create_create'.",
]:
    """Create a batch of companies with properties and associations.

    This tool allows you to create multiple companies in one request. Each company can have specific property values and can be associated with other CRM records. Use this when needing to add several companies at once along with their details and relationships.

    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["CREATEBATCHOFCOMPANIES"],
            "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["CREATEBATCHOFCOMPANIES"] + "\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["CREATEBATCHOFCOMPANIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFCOMPANIES"],
        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=["crm.objects.companies.write"]))
async def delete_companies_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-/crm/v3/objects/companies/batch/archive_archive'.",
]:
    """Delete a batch of companies by ID in HubSpot CRM.

    This tool deletes a batch of companies in HubSpot CRM using their IDs. Deleted companies can be restored within 90 days.

    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["DELETECOMPANIESBATCH"],
            "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["DELETECOMPANIESBATCH"] + "\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["DELETECOMPANIESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/companies/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["DELETECOMPANIESBATCH"],
        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=["crm.objects.companies.write"]))
async def merge_company_records(
    context: ToolContext,
    company_id_to_merge: Annotated[str, "The ID of the company to merge into the primary company."],
    primary_company_id: Annotated[
        str, "The ID of the primary company into which the other company will be merged."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/objects/companies/merge_merge'."
]:
    """Merge two company records in HubSpot CRM.

    Use this tool to merge two company records into a single record in HubSpot CRM, ensuring data consistency."""  # noqa: E501
    request_data: Any = {
        "objectIdToMerge": company_id_to_merge,
        "primaryObjectId": primary_company_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/crm/v3/objects/companies/merge",
        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=["crm.objects.contacts.read"]))
async def batch_read_contacts(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to True to return only archived contacts. False excludes them.  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-/crm/v3/objects/contacts/batch/read'."
]:
    """Retrieve multiple contacts using internal IDs or unique properties.

    Use this tool to read a batch of contacts from HubSpot CRM by providing their internal IDs or unique property values. Ideal for situations where multiple contact details are needed simultaneously.

    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["BATCHREADCONTACTS"],
            "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["BATCHREADCONTACTS"] + "\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["BATCHREADCONTACTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHREADCONTACTS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.contacts.read"]))
async def get_contact_details(
    context: ToolContext,
    contact_identifier: Annotated[
        str, "The ID or unique property value used to identify the contact in HubSpot CRM."
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history of previous values. Ignored if properties are not present.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List of properties to include in the response for the contact. If absent on the object, they will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The property name with unique values for the contact type, used to identify the object.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/contacts/{contactId}'."
]:
    """Retrieve detailed information about a specific contact.

    This tool fetches detailed information about a contact from HubSpot CRM, identified by `contactId`. It provides an option to specify which properties to return using query parameters. Use this tool when you need to access in-depth details about a contact, such as their internal object ID or any unique property value."""  # 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/crm/v3/objects/contacts/{contactId}".format(  # noqa: UP032
            contactId=contact_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def delete_contact(
    context: ToolContext,
    contact_id: Annotated[
        str, "The unique identifier of the contact to be deleted and moved to the recycling bin."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/contacts/{contactId}'."
]:
    """Delete a contact and move it to the recycling bin.

    Use this tool to delete a contact in HubSpot CRM, identified by `contactId`, moving it to the recycling bin."""  # 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/crm/v3/objects/contacts/{contactId}".format(  # noqa: UP032
            contactId=contact_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=["crm.objects.contacts.write"]))
async def update_contact_information(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    contact_id: Annotated[
        str | None,
        "The unique ID or property value used to identify the contact for the update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the property name with unique values for identifying the contact, such as email or phone number.  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-/crm/v3/objects/contacts/{contactId}'."
]:
    """Update specific fields of a contact in HubSpot CRM.

    Use this tool to perform a partial update of a contact's information in HubSpot CRM. Identify the contact by `{contactId}` and specify fields to update. Properties not intended for update are ignored. Pass an empty string to clear values.

    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["UPDATECONTACTINFORMATION"],
            "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 contact_id:
        missing_params.append(("contact_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["UPDATECONTACTINFORMATION"] + "\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["UPDATECONTACTINFORMATION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/{contactId}".format(  # noqa: UP032
            contactId=contact_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECONTACTINFORMATION"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def merge_contacts(
    context: ToolContext,
    contact_id_to_merge: Annotated[
        str, "The ID of the contact object that will be merged into the primary contact."
    ],
    primary_contact_id: Annotated[
        str,
        "The unique identifier of the primary contact that will remain after merging. This contact's information will be retained.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/objects/contacts/merge'."
]:
    """Merges two contacts into one in HubSpot CRM.

    Use this tool to merge two existing contacts within HubSpot CRM. It consolidates contact information, ensuring that no duplicate entries exist for the same individual."""  # noqa: E501
    request_data: Any = {
        "objectIdToMerge": contact_id_to_merge,
        "primaryObjectId": primary_contact_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/crm/v3/objects/contacts/merge",
        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=["crm.objects.contacts.write"]))
async def archive_contacts_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-/crm/v3/objects/contacts/batch/archive'."
]:
    """Archive a batch of contacts by ID in HubSpot CRM.

    Use this tool to archive multiple contacts simultaneously in HubSpot CRM by providing their IDs. This can be useful for managing or cleaning up contact lists efficiently.

    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["ARCHIVECONTACTSBATCH"],
            "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["ARCHIVECONTACTSBATCH"] + "\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["ARCHIVECONTACTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECONTACTSBATCH"],
        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=["crm.objects.contacts.write"]))
async def create_batch_contacts(
    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-/crm/v3/objects/contacts/batch/create'."
]:
    """Create a batch of contacts in HubSpot CRM.

    Use this tool to create multiple contacts in HubSpot CRM at once. Ideal for bulk uploading contact information.

    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["CREATEBATCHCONTACTS"],
            "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["CREATEBATCHCONTACTS"] + "\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["CREATEBATCHCONTACTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHCONTACTS"],
        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=["crm.objects.contacts.write"]))
async def update_batch_contacts(
    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-/crm/v3/objects/contacts/batch/update'."
]:
    """Update a batch of contacts in HubSpot CRM.

    Use this tool to update multiple contacts simultaneously in HubSpot CRM. It is helpful when you need to make changes to several contact records 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["UPDATEBATCHCONTACTS"],
            "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["UPDATEBATCHCONTACTS"] + "\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["UPDATEBATCHCONTACTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHCONTACTS"],
        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=["crm.objects.contacts.write"]))
async def gdpr_delete_contact(
    context: ToolContext,
    contact_identifier: Annotated[
        str,
        "The unique ID or email used to identify the contact for deletion. Use 'email' in conjunction with 'id_property' if identifying by email.",  # noqa: E501
    ],
    contact_identifier_property: Annotated[
        str | None,
        "Specify 'email' to identify the contact by email address. If not using email, specify another unique identifier property.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/objects/contacts/gdpr-delete'."
]:
    """Permanently delete a contact for GDPR compliance.

    Use this tool to permanently delete a contact and all associated content to comply with GDPR. You can identify the contact by email address using the 'idProperty'. If the email isn't found, it will be added to a blocklist to avoid future use."""  # noqa: E501
    request_data: Any = {"objectId": contact_identifier, "idProperty": contact_identifier_property}
    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/crm/v3/objects/contacts/gdpr-delete",
        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=["crm.objects.contacts.read"]))
async def get_contacts(
    context: ToolContext,
    contact_properties_to_retrieve: Annotated[
        list[str] | None,
        "A list of properties to include in the response. Non-existent properties will be ignored.",
    ] = None,
    max_results_per_page: Annotated[
        int | None, "The maximum number of contact results to return per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token indicating the last read resource, used for pagination to retrieve more results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to return with their history of previous values, reducing the max number of objects per request.",  # noqa: E501
    ] = None,
    retrieve_associated_ids: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If associations do not exist, they will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Whether to return only results that have been archived. Use 'true' for archived only.",
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/contacts'."]:
    """Retrieve a page of contacts from HubSpot CRM.

    This tool retrieves a page of contacts from the HubSpot CRM. You can specify which properties you want to be returned by controlling the `properties` 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/crm/v3/objects/contacts",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": contact_properties_to_retrieve,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_ids,
            "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=["crm.objects.contacts.write"]))
async def create_hubspot_contact(
    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-/crm/v3/objects/contacts'."]:
    """Create a contact in HubSpot CRM and retrieve its details.

    Use this tool to add a new contact to HubSpot CRM by specifying the desired properties. It returns a copy of the contact, including its unique ID.

    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["CREATEHUBSPOTCONTACT"],
            "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["CREATEHUBSPOTCONTACT"] + "\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["CREATEHUBSPOTCONTACT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTCONTACT"],
        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=["crm.objects.contacts.read"]))
async def search_contacts(
    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-/crm/v3/objects/contacts/search'."
]:
    """Search contacts in HubSpot CRM.

    Use this tool to search for contacts within the HubSpot CRM. It is useful for retrieving contact information based on search criteria.

    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["SEARCHCONTACTS"],
            "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["SEARCHCONTACTS"] + "\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["SEARCHCONTACTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCONTACTS"],
        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=["crm.objects.contacts.write"]))
async def upsert_contact_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-/crm/v3/objects/contacts/batch/upsert'."
]:
    """Upsert a batch of contacts in HubSpot CRM.

    This tool is used to upsert a batch of contacts in HubSpot CRM. It allows you to define property values for each contact record, making it ideal for updating or creating multiple contacts 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["UPSERTCONTACTBATCH"],
            "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["UPSERTCONTACTBATCH"] + "\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["UPSERTCONTACTBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/contacts/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCONTACTBATCH"],
        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=["crm.objects.courses.read"]))
async def get_courses(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for; ignored if non-existent.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "Specify the maximum number of courses to display in a single page of results."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token of the last successfully read resource, used to fetch the next page of results.",  # noqa: E501
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "Comma separated list of properties to include in the response. Ignored if not present on requested objects.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history. This can reduce the number of courses per request.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. False includes non-archived results.",
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-410'."]:
    """Fetch a page of courses from HubSpot CRM.

    Use this tool to read and retrieve information about a page of courses from HubSpot CRM. The response can be tailored using the `properties` query parameter to control what specific data is returned."""  # 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/crm/v3/objects/0-410",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.courses.write"]))
async def create_hubspot_course(
    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-/crm/v3/objects/0-410'."]:
    """Create a course in HubSpot CRM and return its details.

    Use this tool to create a new course in HubSpot CRM with specified properties. It returns the course details, including the generated ID, upon successful creation.

    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["CREATEHUBSPOTCOURSE"],
            "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["CREATEHUBSPOTCOURSE"] + "\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["CREATEHUBSPOTCOURSE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTCOURSE"],
        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=["crm.objects.courses.write"]))
async def archive_courses_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-/crm/v3/objects/0-410/batch/archive'."
]:
    """Archive a batch of courses by ID.

    Use this tool to archive multiple courses at once by providing their IDs. This simplifies the management of courses by automating the archiving process.

    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["ARCHIVECOURSESBATCH"],
            "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["ARCHIVECOURSESBATCH"] + "\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["ARCHIVECOURSESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECOURSESBATCH"],
        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=["crm.objects.courses.read"]))
async def get_course_details(
    context: ToolContext,
    course_id: Annotated[
        str,
        "The unique identifier for the course. Use this to specify which course details to retrieve.",  # noqa: E501
    ],
    include_properties: Annotated[
        list[str] | None,
        "A list of property names to include in the response. Any missing properties will be ignored.",  # noqa: E501
    ] = None,
    include_properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to retrieve with their history of changes. Input as a list, each entry being a property name.",  # noqa: E501
    ] = None,
    retrieve_associated_object_types: Annotated[
        list[str] | None,
        "A list of object types for retrieving associated IDs. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    unique_property_name: Annotated[
        str | None, "Specify the name of a unique property for the course object."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-410/{courseId}'."
]:
    """Fetch details of a course using the course ID.

    Use this tool to retrieve detailed information about a course identified by its ID. The tool provides access to all course-related data, leveraging the course ID or any unique property value specified."""  # 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/crm/v3/objects/0-410/{courseId}".format(courseId=course_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": include_properties,
            "propertiesWithHistory": include_properties_with_history,
            "associations": retrieve_associated_object_types,
            "archived": return_only_archived,
            "idProperty": unique_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=["crm.objects.courses.write"]))
async def delete_course(
    context: ToolContext,
    course_id: Annotated[
        str,
        "The unique identifier for the course to be deleted. This identifier is used to locate the specific course in the CRM system.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/0-410/{courseId}'."
]:
    """Delete a course by moving it to the recycling bin.

    This tool deletes a specific course in the CRM by moving it to the recycling bin, identified by the provided `courseId`. Use this when you need to remove a course from the CRM records."""  # 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/crm/v3/objects/0-410/{courseId}".format(courseId=course_id),  # noqa: UP032
        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=["crm.objects.courses.write"]))
async def update_hubspot_course(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    course_identifier: Annotated[
        str | None,
        "The unique identifier for the HubSpot course. It can be the internal course ID or a unique property value specified by `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The property name with unique values for identifying the object to update. Use it if not using `courseId`.  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-/crm/v3/objects/0-410/{courseId}'."
]:
    """Update specific properties of a HubSpot course object.

    Use this tool to perform a partial update on a course object in HubSpot CRM. Update is identified by either the `{courseId}` or a unique property via `idProperty`. Only properties provided will be changed, while read-only or non-existent properties will cause an error. Properties can be cleared by providing an empty string.

    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["UPDATEHUBSPOTCOURSE"],
            "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 course_identifier:
        missing_params.append(("course_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["UPDATEHUBSPOTCOURSE"] + "\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["UPDATEHUBSPOTCOURSE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/{courseId}".format(  # noqa: UP032
            courseId=course_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTCOURSE"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.courses.write"]))
async def create_batch_of_courses(
    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-/crm/v3/objects/0-410/batch/create'."
]:
    """Create a batch of courses in CRM.

    Use this tool to add multiple courses at once to HubSpot CRM. Ideal for instances where you need to create several course entries simultaneously.

    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["CREATEBATCHOFCOURSES"],
            "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["CREATEBATCHOFCOURSES"] + "\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["CREATEBATCHOFCOURSES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFCOURSES"],
        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=["crm.objects.courses.write"]))
async def batch_upsert_records(
    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-/crm/v3/objects/0-410/batch/upsert'."
]:
    """Create or update HubSpot CRM records via unique identifier.

    This tool creates or updates HubSpot CRM records based on a unique property identifier specified by the `idProperty` parameter. It is useful for ensuring records are maintained accurately in the CRM.

    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["BATCHUPSERTRECORDS"],
            "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["BATCHUPSERTRECORDS"] + "\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["BATCHUPSERTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHUPSERTRECORDS"],
        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=["crm.objects.courses.read"]))
async def hubspot_crm_search_objects(
    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-/crm/v3/objects/0-410/search'."
]:
    """Search and retrieve objects from HubSpot CRM.

    Use this tool to perform a search for specific objects within the HubSpot CRM. It should be called when you need to find and retrieve detailed information about CRM objects based on search criteria.

    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["HUBSPOTCRMSEARCHOBJECTS"],
            "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["HUBSPOTCRMSEARCHOBJECTS"] + "\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["HUBSPOTCRMSEARCHOBJECTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["HUBSPOTCRMSEARCHOBJECTS"],
        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=["crm.objects.courses.write"]))
async def update_courses_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-/crm/v3/objects/0-410/batch/update'."
]:
    """Update multiple courses in a batch by ID or unique properties.

    Use this tool to update a batch of courses in the CRM system by specifying their internal IDs or unique property values. This is useful for making bulk updates efficiently.

    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["UPDATECOURSESBATCH"],
            "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["UPDATECOURSESBATCH"] + "\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["UPDATECOURSESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECOURSESBATCH"],
        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=["crm.objects.courses.read"]))
async def retrieve_hubspot_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_records: Annotated[
        bool | None,
        "Set to true to return only archived records; false to exclude archived records in HubSpot CRM.  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-/crm/v3/objects/0-410/batch/read'."
]:
    """Retrieve HubSpot CRM records by ID or unique property.

    This tool retrieves HubSpot CRM records using either the record ID or a custom unique value property specified by the `idProperty`. It's useful for accessing multiple records within the CRM at once.

    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["RETRIEVEHUBSPOTRECORDS"],
            "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["RETRIEVEHUBSPOTRECORDS"] + "\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["RETRIEVEHUBSPOTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-410/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTRECORDS"],
        params=remove_none_values({"archived": return_archived_records}),
        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=["crm.objects.owners.read"]))
async def retrieve_owners_list(
    context: ToolContext,
    filter_by_email: Annotated[
        str | None,
        "Specify an email address to filter the list of owners returned. Only the owner with this exact email will be retrieved.",  # noqa: E501
    ] = None,
    include_archived: Annotated[
        bool | None, "Set to true to include archived owners in the retrieved list."
    ] = False,
    owners_list_limit: Annotated[
        int | None, "The maximum number of owners to return per page. Provide an integer value."
    ] = 100,
    pagination_cursor_after: Annotated[
        str | None,
        "A cursor to get the next page of results, indicating the last result shown from the previous request.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/owners/'."]:
    """Retrieve a list of owners from the HubSpot CRM account.

    Use this tool to obtain a paginated list of all owners available in a HubSpot CRM account. This can be useful for managing owner-related data or assignments."""  # 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/crm/v3/owners/",
        method="GET",
        params=remove_none_values({
            "email": filter_by_email,
            "after": pagination_cursor_after,
            "limit": owners_list_limit,
            "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=["crm.objects.owners.read"]))
async def retrieve_owner_details(
    context: ToolContext,
    owner_id: Annotated[
        int, "The unique ID of the CRM owner. Use this to retrieve the owner's details."
    ],
    include_archived: Annotated[
        bool | None, "Set to true to include archived owners in the retrieved details."
    ] = False,
    owner_id_type: Annotated[
        str | None, "Specify whether the 'ownerId' refers to an 'id' or 'userId'."
    ] = "id",
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/owners/{ownerId}'."]:
    """Retrieve details of a specific CRM owner by ID.

    This tool retrieves details of a specific owner in the HubSpot CRM using either their 'id' or 'userId'. Use it when you need comprehensive information about a particular CRM owner."""  # 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/crm/v3/owners/{ownerId}".format(ownerId=owner_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"idProperty": owner_id_type, "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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def batch_retrieve_hubspot_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specify the type of CRM object to retrieve, such as 'contact', 'deal', or 'company'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    return_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Use false to include active records.  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-/crm/v3/objects/{objectType}/batch/read_read'.",
]:
    """Retrieve HubSpot CRM records using batch read.

    This tool retrieves records from HubSpot CRM by record ID or custom unique property in a batch. Use it when you need to access multiple entities by their specific identifiers.

    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["BATCHRETRIEVEHUBSPOTRECORDS"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["BATCHRETRIEVEHUBSPOTRECORDS"]
                + "\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["BATCHRETRIEVEHUBSPOTRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/batch/read".format(  # noqa: UP032
            objectType=crm_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHRETRIEVEHUBSPOTRECORDS"],
        params=remove_none_values({"archived": return_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def fetch_hubspot_object_by_id(
    context: ToolContext,
    object_identifier: Annotated[
        str,
        "The unique identifier of the HubSpot CRM object to retrieve. This can be the internal object ID or another unique property value if specified by the `id_property` parameter.",  # noqa: E501
    ],
    object_type: Annotated[
        str,
        "The type of the HubSpot CRM object you want to retrieve. Examples include 'contact', 'company', and 'deal'.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Specify object types to retrieve associated IDs for, separated by commas. Non-existing associations will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return along with their historical values. Ignored if properties are not present.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Set to false to include all results.",
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List of properties to return for the object. Ignored if not present on the object.",
    ] = None,
    unique_property_name: Annotated[
        str | None, "Specify the property name that uniquely identifies the object."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/{objectType}/{objectId}_getById'.",
]:
    """Retrieve a HubSpot CRM object using its unique ID.

    Use this tool to fetch the details of a HubSpot CRM object by its unique ID. Specify the object type and optional parameters to control the returned 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/crm/v3/objects/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=object_type, objectId=object_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived_results,
            "idProperty": unique_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def move_object_to_recycle_bin(
    context: ToolContext,
    object_id: Annotated[
        str,
        "The unique identifier for the CRM object to be moved to the recycling bin. This ID specifies which object within the CRM will be affected.",  # noqa: E501
    ],
    object_type: Annotated[
        str, "Type of the CRM object to be moved, such as 'contacts', 'companies', etc."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/{objectType}/{objectId}_archive'.",
]:
    """Move a CRM object to the recycling bin.

    Use this tool to move a specified CRM object to the recycling bin using its object type and ID. This is useful for archiving or removing unnecessary data in HubSpot CRM."""  # 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/crm/v3/objects/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=object_type, objectId=object_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def modify_hubspot_object(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubspot_object_type: Annotated[
        str | None,
        "Specify the type of HubSpot CRM object (e.g., 'contacts', 'companies', 'deals').  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    object_identifier: Annotated[
        str | None,
        "The internal ID of the HubSpot object to update. Use a string format.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for identifying the object. Use when the default object ID isn't used.  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-/crm/v3/objects/{objectType}/{objectId}_update'.",
]:
    """Update specific properties of a HubSpot CRM object.

    Use this tool to perform partial updates on HubSpot CRM objects by specifying the object type and ID. You can update properties by providing new values, but ensure they aren't read-only or non-existent. Clear properties by passing an empty string.

    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["MODIFYHUBSPOTOBJECT"],
            "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 hubspot_object_type:
        missing_params.append(("hubspot_object_type", "path"))
    if not object_identifier:
        missing_params.append(("object_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["MODIFYHUBSPOTOBJECT"] + "\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["MODIFYHUBSPOTOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/{objectId}".format(  # noqa: UP032
            objectType=hubspot_object_type, objectId=object_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["MODIFYHUBSPOTOBJECT"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def merge_hubspot_objects(
    context: ToolContext,
    object_id_to_merge: Annotated[
        str,
        "The ID of the object to be merged into the primary object. It must be of the same type as the primary object.",  # noqa: E501
    ],
    object_type_to_merge: Annotated[
        str, "The type of HubSpot CRM object to merge, such as 'contacts' or 'companies'."
    ],
    primary_object_id: Annotated[
        str, "The ID of the object that will remain after the merge. Provide as a string."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/crm/v3/objects/{objectType}/merge_merge'.",
]:
    """Merge two HubSpot CRM objects of the same type.

    Use this tool to merge two objects within HubSpot CRM that have the same object type, such as contacts or companies. It is useful for consolidating duplicate records into a single, unified entry."""  # noqa: E501
    request_data: Any = {
        "objectIdToMerge": object_id_to_merge,
        "primaryObjectId": primary_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/crm/v3/objects/{objectType}/merge".format(  # noqa: UP032
            objectType=object_type_to_merge
        ),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def archive_hubspot_objects_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubspot_object_type: Annotated[
        str | None,
        "Specifies the type of HubSpot CRM objects to archive (e.g., 'contacts', 'companies').  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-/crm/v3/objects/{objectType}/batch/archive_archive'.",
]:
    """Archive a batch of HubSpot CRM objects by ID.

    Use this tool to archive multiple objects in HubSpot CRM by specifying their IDs and object type, such as 'contacts' or 'companies'.

    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["ARCHIVEHUBSPOTOBJECTSBATCH"],
            "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 hubspot_object_type:
        missing_params.append(("hubspot_object_type", "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["ARCHIVEHUBSPOTOBJECTSBATCH"]
                + "\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["ARCHIVEHUBSPOTOBJECTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/batch/archive".format(  # noqa: UP032
            objectType=hubspot_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEHUBSPOTOBJECTSBATCH"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_hubspot_objects_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "The type of HubSpot CRM object to update, such as 'contacts', 'companies', 'deals', or 'tickets'.  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-/crm/v3/objects/{objectType}/batch/update_update'.",
]:
    """Update multiple HubSpot CRM objects in a batch.

    Use this tool to update a batch of objects in HubSpot CRM using internal IDs or unique property values. Useful for synchronizing or modifying multiple CRM records at once.

    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["UPDATEHUBSPOTOBJECTSBATCH"],
            "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 object_type:
        missing_params.append(("object_type", "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["UPDATEHUBSPOTOBJECTSBATCH"] + "\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["UPDATEHUBSPOTOBJECTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/batch/update".format(  # noqa: UP032
            objectType=object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTOBJECTSBATCH"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_hubspot_objects_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    hubspot_object_type: Annotated[
        str | None,
        "Specifies the type of object to create in HubSpot, such as 'contacts', 'deals', or 'companies'.  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-/crm/v3/objects/{objectType}/batch/create_create'.",
]:
    """Create a batch of objects in HubSpot CRM.

    Use this tool to create multiple objects at once in HubSpot CRM. Suitable for handling bulk operations where several objects need to be created simultaneously.

    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["CREATEHUBSPOTOBJECTSBATCH"],
            "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 hubspot_object_type:
        missing_params.append(("hubspot_object_type", "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["CREATEHUBSPOTOBJECTSBATCH"] + "\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["CREATEHUBSPOTOBJECTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/batch/create".format(  # noqa: UP032
            objectType=hubspot_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTOBJECTSBATCH"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def update_or_create_hubspot_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specifies the type of CRM object to act upon, such as 'contacts', 'companies', etc.  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-/crm/v3/objects/{objectType}/batch/upsert'.",
]:
    """Create or update HubSpot CRM records in bulk.

    This tool is used to batch upsert records in HubSpot CRM based on a unique identifier. It's ideal for managing large datasets where records may need to be created or updated if they already exist.

    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["UPDATEORCREATEHUBSPOTRECORDS"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["UPDATEORCREATEHUBSPOTRECORDS"]
                + "\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["UPDATEORCREATEHUBSPOTRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/batch/upsert".format(  # noqa: UP032
            objectType=crm_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEORCREATEHUBSPOTRECORDS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_hubspot_objects_page(
    context: ToolContext,
    object_type: Annotated[
        str,
        "Specify the type of CRM object to retrieve, such as 'contacts', 'companies', or 'deals'.",
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated object types to retrieve associated IDs for. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token used to retrieve the next page of results. Obtained from the `paging.next.after` property of a previous response.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List the properties to return with their historical values in the CRM objects.",
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "List of properties to include in the response. Ignored if not present on the object(s).",
    ] = None,
    return_archived_results_only: Annotated[
        bool | None, "Return only the archived results if set to true."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/{objectType}_getPage'."
]:
    """Retrieve a page of HubSpot CRM objects.

    Use this tool to read a page of objects from HubSpot CRM. Specify the desired properties through the `properties` query parameter to control the returned 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/crm/v3/objects/{objectType}".format(objectType=object_type),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def create_crm_object(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specify the type of CRM object to create, such as 'contact', 'company', or 'deal'.  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-/crm/v3/objects/{objectType}_create'."
]:
    """Create a CRM object and retrieve its details.

    This tool creates a new CRM object in HubSpot with specified properties and returns the object, including its ID. Use it to add contacts, companies, deals, etc., to your HubSpot CRM.

    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["CREATECRMOBJECT"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["CREATECRMOBJECT"] + "\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["CREATECRMOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}".format(objectType=crm_object_type),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECRMOBJECT"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "oauth",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def search_hubspot_objects(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "Specify the type of object to search for in HubSpot, such as contacts, companies, or deals.  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-/crm/v3/objects/{objectType}/search_doSearch'.",
]:
    """Perform a search on HubSpot CRM objects by type.

    Use this tool to search for objects in HubSpot CRM by specifying the object type. It helps in locating contacts, companies, deals, and other entities based on defined criteria.

    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["SEARCHHUBSPOTOBJECTS"],
            "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 object_type:
        missing_params.append(("object_type", "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["SEARCHHUBSPOTOBJECTS"] + "\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["SEARCHHUBSPOTOBJECTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/{objectType}/search".format(  # noqa: UP032
            objectType=object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTOBJECTS"],
        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=["crm.objects.deals.write"]))
async def update_multiple_deals(
    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-/crm/v3/objects/0-3/batch/update_update'."
]:
    """Update multiple deals in the CRM system.

    This tool updates multiple deals using their internal IDs or unique property values in the CRM. It should be called when you need to modify several deals at once within the HubSpot CRM.

    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["UPDATEMULTIPLEDEALS"],
            "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["UPDATEMULTIPLEDEALS"] + "\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["UPDATEMULTIPLEDEALS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLEDEALS"],
        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=["crm.objects.deals.read"]))
async def search_deals(
    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-/crm/v3/objects/0-3/search_doSearch'."
]:
    """Search for deals using specified criteria and filters.

    This tool searches for deals in the HubSpot CRM using the provided criteria and filters. It should be called when there's a need to find deals that match specific conditions.

    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["SEARCHDEALS"],
            "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["SEARCHDEALS"] + "\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["SEARCHDEALS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHDEALS"],
        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=["crm.objects.deals.write"]))
async def create_or_update_hubspot_records(
    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-/crm/v3/objects/0-3/batch/upsert_upsert'."
]:
    """Create or update HubSpot CRM records using unique properties.

    This tool is used to create or update HubSpot CRM records by leveraging unique property values for identification. It ensures that records are upserted based on the provided unique identifier.

    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["CREATEORUPDATEHUBSPOTRECORDS"],
            "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["CREATEORUPDATEHUBSPOTRECORDS"]
                + "\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["CREATEORUPDATEHUBSPOTRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATEHUBSPOTRECORDS"],
        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=["crm.objects.deals.write"]))
async def archive_multiple_deals(
    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-/crm/v3/objects/0-3/batch/archive_archive'.",
]:
    """Archive multiple deals using their IDs in HubSpot CRM.

    This tool is used to archive multiple deals in HubSpot CRM by providing their IDs. It should be called when there is a need to batch archive deals efficiently.

    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["ARCHIVEMULTIPLEDEALS"],
            "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["ARCHIVEMULTIPLEDEALS"] + "\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["ARCHIVEMULTIPLEDEALS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEMULTIPLEDEALS"],
        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=["crm.objects.deals.read"]))
async def retrieve_crm_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    retrieve_only_archived_records: Annotated[
        bool | None,
        "Set to true to retrieve only archived CRM records.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/0-3/batch/read_read'."
]:
    """Retrieve CRM records by ID or custom unique property.

    This tool retrieves CRM records using either their record ID or a custom unique value property specified by the `idProperty` parameter. It is useful for accessing detailed information about specific records stored in the CRM system.

    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["RETRIEVECRMRECORDS"],
            "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["RETRIEVECRMRECORDS"] + "\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["RETRIEVECRMRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVECRMRECORDS"],
        params=remove_none_values({"archived": retrieve_only_archived_records}),
        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=["crm.objects.deals.read"]))
async def get_hubspot_deal_by_id(
    context: ToolContext,
    deal_id: Annotated[str, "The unique identifier of the deal to retrieve from HubSpot CRM."],
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to return with their history of values. Use a comma-separated list.",
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List of properties to be returned in the response. Ignored if not present on the object.",
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the unique property name used to identify the deal. It defaults to an internal object ID if not provided.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-3/{dealId}_getById'."
]:
    """Retrieve HubSpot CRM deal information by Deal ID.

    Use this tool to fetch detailed information about a specific deal in HubSpot CRM using the deal's unique ID. The response includes properties specified by the user."""  # 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/crm/v3/objects/0-3/{dealId}".format(dealId=deal_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_archived_only,
            "idProperty": unique_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=["crm.objects.deals.write"]))
async def archive_deal_in_hubspot(
    context: ToolContext,
    deal_id: Annotated[str, "The unique identifier of the deal to be archived in HubSpot CRM."],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/0-3/{dealId}_archive'."
]:
    """Archives a specific deal in HubSpot CRM.

    Use this tool to move a deal identified by `dealId` to the recycling bin in HubSpot CRM, effectively archiving it."""  # 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/crm/v3/objects/0-3/{dealId}".format(dealId=deal_id),  # noqa: UP032
        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=["crm.objects.deals.write"]))
async def update_hubspot_deal(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    deal_identifier: Annotated[
        str | None,
        "The unique identifier of the deal to be updated in HubSpot CRM. Can be internal ID or unique property value.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property to identify the deal instead of `dealId`.  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-/crm/v3/objects/0-3/{dealId}_update'."
]:
    """Update a specific deal in HubSpot CRM.

    Use this tool to perform a partial update on a deal in HubSpot CRM by specifying the deal's internal ID or a unique property value. Only existing properties can be updated, and read-only properties will not be accepted.

    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["UPDATEHUBSPOTDEAL"],
            "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 deal_identifier:
        missing_params.append(("deal_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["UPDATEHUBSPOTDEAL"] + "\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["UPDATEHUBSPOTDEAL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/{dealId}".format(dealId=deal_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTDEAL"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.deals.write"]))
async def create_multiple_deals(
    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-/crm/v3/objects/0-3/batch/create_create'."
]:
    """Create multiple deals in HubSpot CRM in one request.

    Use this tool to batch create multiple deals in HubSpot CRM efficiently. It should be called when you need to add several deals at once, reducing the number of individual requests.

    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["CREATEMULTIPLEDEALS"],
            "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["CREATEMULTIPLEDEALS"] + "\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["CREATEMULTIPLEDEALS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMULTIPLEDEALS"],
        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=["crm.objects.deals.read"]))
async def get_deals_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for, separated by commas. If associations don't exist, they're ignored.",  # noqa: E501
    ] = None,
    deal_properties: Annotated[
        list[str] | None,
        "List the properties to include in the response as a comma-separated string. Ignored if not present.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None, "The token for the paging cursor to retrieve the next page of results."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of deal properties for which historical values are returned. Usage reduces max results per request.",  # noqa: E501
    ] = None,
    results_limit_per_page: Annotated[
        int | None, "The maximum number of deals to display per page, as an integer."
    ] = 10,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived results; false to include active results."
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-3_getPage'."]:
    """Read a page of deals from the CRM system.

    Use this tool to retrieve a page of deals from HubSpot CRM. You can control the information returned by specifying desired properties through query parameters."""  # 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/crm/v3/objects/0-3",
        method="GET",
        params=remove_none_values({
            "limit": results_limit_per_page,
            "after": paging_cursor_token,
            "properties": deal_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.deals.write"]))
async def create_hubspot_deal(
    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-/crm/v3/objects/0-3_create'."]:
    """Create a new deal in HubSpot CRM.

    Use this tool to create a new deal in HubSpot CRM by specifying the necessary properties. It returns the created deal object along with its unique ID.

    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["CREATEHUBSPOTDEAL"],
            "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["CREATEHUBSPOTDEAL"] + "\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["CREATEHUBSPOTDEAL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-3",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTDEAL"],
        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=["crm.objects.deals.write"]))
async def merge_deals(
    context: ToolContext,
    deal_id_to_merge: Annotated[str, "The ID of the deal to be merged into the primary deal."],
    primary_deal_id: Annotated[str, "The ID of the primary deal that will remain after merging."],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/objects/0-3/merge_merge'."
]:
    """Combine two deals into a single unified deal in HubSpot CRM.

    Use this tool to merge two deals of the same type into one within HubSpot CRM. It is useful when consolidating duplicate or related deals into a single entry."""  # noqa: E501
    request_data: Any = {"objectIdToMerge": deal_id_to_merge, "primaryObjectId": primary_deal_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/crm/v3/objects/0-3/merge",
        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=["crm.dealsplits.read_write"]))
async def manage_deal_splits(
    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-/crm/v3/objects/deals/splits/batch/upsert_upsert'.",
]:
    """Create or replace deal splits for specific deals.

    This tool is used to create or update deal splits for the specified deal IDs. The deal split percentages must add up to 100% and can have up to 8 decimal places. It is useful for managing deal contributions among different stakeholders.

    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["MANAGEDEALSPLITS"],
            "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["MANAGEDEALSPLITS"] + "\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["MANAGEDEALSPLITS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/deals/splits/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["MANAGEDEALSPLITS"],
        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=["crm.dealsplits.read_write"]))
async def read_batch_deal_splits(
    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-/crm/v3/objects/deals/splits/batch/read_read'.",
]:
    """Fetch a batch of deal split objects by deal ID.

    This tool reads a batch of deal split objects using their associated deal object internal IDs, allowing access to multiple deal splits in one request.

    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["READBATCHDEALSPLITS"],
            "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["READBATCHDEALSPLITS"] + "\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["READBATCHDEALSPLITS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/deals/splits/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["READBATCHDEALSPLITS"],
        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=["crm.objects.line_items.read"]))
async def search_discounts(
    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-/crm/v3/objects/discounts/search'."
]:
    """Search for discounts in the HubSpot CRM.

    Use this tool to search for discount records in the HubSpot CRM. It is useful when you need to find specific discounts based on certain criteria.

    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["SEARCHDISCOUNTS"],
            "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["SEARCHDISCOUNTS"] + "\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["SEARCHDISCOUNTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHDISCOUNTS"],
        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=["crm.objects.line_items.write"]))
async def update_discounts_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-/crm/v3/objects/discounts/batch/update'."
]:
    """Update multiple discounts by ID or unique properties.

    Use this tool to update a batch of discounts in HubSpot CRM by specifying internal IDs or unique property values. It is suitable for scenarios where multiple discount records need to be updated simultaneously.

    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["UPDATEDISCOUNTSBATCH"],
            "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["UPDATEDISCOUNTSBATCH"] + "\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["UPDATEDISCOUNTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEDISCOUNTSBATCH"],
        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=["crm.objects.line_items.read"]))
async def retrieve_discount_details(
    context: ToolContext,
    discount_identifier: Annotated[
        str,
        "The unique identifier for the discount object to retrieve. This can either be the internal ID or a value of a unique property specified by `idProperty`.",  # noqa: E501
    ],
    archived_results_only: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to retrieve along with their value history. Ignored if properties are missing.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return in response. Ignored if not present on the object.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The property name for uniquely identifying the discount object. Use when the property value is not the default ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/discounts/{discountId}'."
]:
    """Retrieve details of a discount by its ID.

    Use this tool to get detailed information about a specific discount object by providing its ID. The tool allows customization of returned properties through query parameters."""  # 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/crm/v3/objects/discounts/{discountId}".format(  # noqa: UP032
            discountId=discount_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": archived_results_only,
            "idProperty": unique_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=["crm.objects.line_items.write"]))
async def delete_discount(
    context: ToolContext,
    discount_identifier: Annotated[
        str, "The unique identifier of the discount object to delete and move to the recycling bin."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/discounts/{discountId}'.",
]:
    """Delete a discount and move it to the recycling bin.

    Use this tool to delete a discount object in HubSpot CRM by moving it to the recycling bin. Call it when you need to remove discounts identified by a specific discount 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/crm/v3/objects/discounts/{discountId}".format(  # noqa: UP032
            discountId=discount_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=["crm.objects.line_items.write"]))
async def update_discount_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    discount_identifier: Annotated[
        str | None,
        "The unique identifier for the discount object. This can be the internal ID or a unique property specified by `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property to identify the discount object. Use this instead of the internal discount ID if needed.  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-/crm/v3/objects/discounts/{discountId}'."
]:
    """Update specific properties of a discount in HubSpot CRM.

    Use this tool to perform a partial update on a discount object in HubSpot CRM. Specify the discount using its internal ID or by a unique property using `idProperty`. Only overwrite existing properties. Errors occur with read-only or non-existent properties. Clear properties by passing an empty string.

    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["UPDATEDISCOUNTDETAILS"],
            "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 discount_identifier:
        missing_params.append(("discount_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["UPDATEDISCOUNTDETAILS"] + "\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["UPDATEDISCOUNTDETAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/{discountId}".format(  # noqa: UP032
            discountId=discount_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEDISCOUNTDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.line_items.write"]))
async def create_batch_discounts_hubspot(
    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-/crm/v3/objects/discounts/batch/create'."
]:
    """Create a batch of discounts in HubSpot.

    Use this tool to create multiple discounts simultaneously in HubSpot CRM. It should be called when batch creation of discounts is needed, providing an efficient way to manage discount entries.

    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["CREATEBATCHDISCOUNTSHUBSPOT"],
            "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["CREATEBATCHDISCOUNTSHUBSPOT"]
                + "\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["CREATEBATCHDISCOUNTSHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHDISCOUNTSHUBSPOT"],
        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=["crm.objects.line_items.write"]))
async def upsert_discount_records(
    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-/crm/v3/objects/discounts/batch/upsert'."
]:
    """Create or update discount records in HubSpot CRM.

    This tool allows the creation or update of discount records in HubSpot CRM using unique property values specified by the `idProperty` query parameter. It is useful for ensuring that discount data is up-to-date and accurately maintained.

    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["UPSERTDISCOUNTRECORDS"],
            "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["UPSERTDISCOUNTRECORDS"] + "\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["UPSERTDISCOUNTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTDISCOUNTRECORDS"],
        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=["crm.objects.line_items.write"]))
async def archive_discounts_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-/crm/v3/objects/discounts/batch/archive'."
]:
    """Archive a batch of discounts by their IDs in HubSpot CRM.

    This tool is used to archive multiple discount objects in HubSpot CRM by providing their IDs. It should be called when there's a need to remove or deactivate several discounts at once for organizational or business 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["ARCHIVEDISCOUNTSBATCH"],
            "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["ARCHIVEDISCOUNTSBATCH"] + "\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["ARCHIVEDISCOUNTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEDISCOUNTSBATCH"],
        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=["crm.objects.line_items.read"]))
async def get_hubspot_discounts(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Unknown associations will be ignored.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "The maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The cursor token for pagination. Use the token from the last successfully read resource to fetch the next page.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history; reduces number of discounts returned per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only archived discounts, false to include all."
    ] = False,
    returned_discount_properties: Annotated[
        list[str] | None,
        "List of properties to include in the response. Comma-separated, ignored if not present.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/discounts'."]:
    """Retrieve a page of discounts from HubSpot CRM.

    Use this tool to read a page of discounts from HubSpot CRM. You can control the returned data using the `properties` 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/crm/v3/objects/discounts",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": returned_discount_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.line_items.write"]))
async def create_discount(
    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-/crm/v3/objects/discounts'."]:
    """Creates a discount and returns its details.

    Use this tool to create a new discount with specified properties in HubSpot CRM. It returns the created discount object along with its ID.

    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["CREATEDISCOUNT"],
            "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["CREATEDISCOUNT"] + "\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["CREATEDISCOUNT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEDISCOUNT"],
        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=["crm.objects.line_items.read"]))
async def retrieve_discount_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    archived: Annotated[
        bool | None,
        "Set to true to return only archived results.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/discounts/batch/read'."
]:
    """Retrieve discount records by ID or custom property.

    This tool retrieves discount records from HubSpot CRM by specifying record IDs or a unique custom property. Use it to fetch detailed information about discounts in a batch.

    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["RETRIEVEDISCOUNTRECORDS"],
            "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["RETRIEVEDISCOUNTRECORDS"] + "\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["RETRIEVEDISCOUNTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/discounts/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEDISCOUNTRECORDS"],
        params=remove_none_values({"archived": archived}),
        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=["crm.objects.contacts.write"]))
async def create_batch_of_emails(
    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-/crm/v3/objects/emails/batch/create_create'.",
]:
    """Create a batch of emails with specified properties.

    This tool is used to create multiple emails at once in a batch, using specified properties, and returns the details of the created email objects. Ideal for situations where several emails need to be generated and saved in the CRM system 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["CREATEBATCHOFEMAILS"],
            "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["CREATEBATCHOFEMAILS"] + "\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["CREATEBATCHOFEMAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFEMAILS"],
        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=["crm.objects.contacts.read"]))
async def retrieve_emails_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If any specified associations don't exist, they will be ignored.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "The maximum number of email results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token for the next page of results, from the `paging.next.after` field of the previous response.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history of previous values. Reduces maximum emails per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to retrieve only archived emails."
    ] = False,
    returned_email_properties: Annotated[
        list[str] | None,
        "List the email properties to be included in the response. Specify as an array of strings representing the desired properties.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/emails_getPage'."
]:
    """Retrieve a page of emails from HubSpot CRM.

    This tool reads a page of emails from HubSpot CRM. You can control which email properties are returned by using the `properties` 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/crm/v3/objects/emails",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": returned_email_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.contacts.write"]))
async def create_hubspot_email(
    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-/crm/v3/objects/emails_create'."
]:
    """Create an email in HubSpot CRM and retrieve its details.

    Use this tool to create a new email in HubSpot CRM by specifying certain properties. It returns the created email object along with its ID, facilitating the organization and tracking of email records within the CRM.

    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["CREATEHUBSPOTEMAIL"],
            "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["CREATEHUBSPOTEMAIL"] + "\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["CREATEHUBSPOTEMAIL"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTEMAIL"],
        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=["crm.objects.contacts.read"]))
async def retrieve_email_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_archived_records: Annotated[
        bool | None,
        "Set to true to return only archived email records from HubSpot CRM.  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-/crm/v3/objects/emails/batch/read_read'."
]:
    """Retrieve email records by ID or custom property.

    This tool retrieves email records from HubSpot CRM by providing record IDs or using a custom unique value property. It should be called when you need details about specific email records stored within HubSpot.

    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["RETRIEVEEMAILRECORDS"],
            "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["RETRIEVEEMAILRECORDS"] + "\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["RETRIEVEEMAILRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEEMAILRECORDS"],
        params=remove_none_values({"archived": only_archived_records}),
        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=["crm.objects.contacts.write"]))
async def create_or_update_hubspot_emails(
    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-/crm/v3/objects/emails/batch/upsert_upsert'.",
]:
    """Create or update HubSpot email records in batch.

    This tool allows you to create or update email records in HubSpot's CRM by using a unique property value to identify the records. It's useful when you need to manage email data efficiently by ensuring existing records are updated and new ones are created where needed.

    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["CREATEORUPDATEHUBSPOTEMAILS"],
            "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["CREATEORUPDATEHUBSPOTEMAILS"]
                + "\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["CREATEORUPDATEHUBSPOTEMAILS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATEHUBSPOTEMAILS"],
        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=["crm.objects.contacts.read"]))
async def retrieve_email_by_id(
    context: ToolContext,
    email_id: Annotated[
        str, "The unique ID or property value of the email object to be retrieved."
    ],
    object_types_for_associated_ids: Annotated[
        list[str] | None,
        "List the object types to retrieve associated IDs. If no associations exist, they will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated property names to include their history of previous values in the response. Non-existent properties will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Set to false to return active results.",
    ] = False,
    returned_properties: Annotated[
        list[str] | None,
        "A list of properties to return for the email object. Properties not present on the object will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the property name that holds unique values for the email object to be retrieved.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/emails/{emailId}_getById'."
]:
    """Retrieve email object details using its ID.

    Use this tool to get detailed information about an email object in HubSpot CRM using its unique ID. You can control the output via query parameters."""  # 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/crm/v3/objects/emails/{emailId}".format(emailId=email_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": returned_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": object_types_for_associated_ids,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def delete_email(
    context: ToolContext,
    email_id: Annotated[str, "The unique identifier of the email to be archived in HubSpot CRM."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/emails/{emailId}_archive'.",
]:
    """Move an email to the recycling bin using its ID.

    This tool moves an email identified by `emailId` to the recycling bin in HubSpot CRM. It should be used when an email needs to be archived or deleted from active view."""  # 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/crm/v3/objects/emails/{emailId}".format(emailId=email_id),  # noqa: UP032
        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=["crm.objects.contacts.write"]))
async def update_email_in_hubspot_crm(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    email_identifier: Annotated[
        str | None,
        "The unique identifier for the email object, either the internal ID or a unique property value.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Name of a unique property for identifying the email object, used instead of default ID.  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-/crm/v3/objects/emails/{emailId}_update'.",
]:
    """Updates an email object in HubSpot CRM with new property values.

    This tool updates properties of an email object in HubSpot CRM, identified by the internal ID or a unique property. Read-only or nonexistent properties will cause errors, and properties can be cleared by passing an empty string.

    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["UPDATEEMAILINHUBSPOTCRM"],
            "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 email_identifier:
        missing_params.append(("email_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["UPDATEEMAILINHUBSPOTCRM"] + "\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["UPDATEEMAILINHUBSPOTCRM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/{emailId}".format(  # noqa: UP032
            emailId=email_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEEMAILINHUBSPOTCRM"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def archive_emails_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-/crm/v3/objects/emails/batch/archive_archive'.",
]:
    """Archive a batch of emails by their IDs.

    Use this tool to archive multiple emails in the HubSpot CRM by providing their unique IDs. Useful for organizing or cleaning up email records.

    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["ARCHIVEEMAILSBATCH"],
            "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["ARCHIVEEMAILSBATCH"] + "\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["ARCHIVEEMAILSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEEMAILSBATCH"],
        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=["crm.objects.contacts.write"]))
async def update_batch_emails(
    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-/crm/v3/objects/emails/batch/update_update'.",
]:
    """Update a batch of emails by their IDs or unique properties.

    Use this tool to update multiple emails in bulk using their internal IDs or unique property values in HubSpot CRM.

    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["UPDATEBATCHEMAILS"],
            "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["UPDATEBATCHEMAILS"] + "\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["UPDATEBATCHEMAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHEMAILS"],
        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=["crm.objects.contacts.read"]))
async def search_emails(
    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-/crm/v3/objects/emails/search_doSearch'."
]:
    """Search for emails based on specified query parameters.

    Use this tool to perform a search for emails in the CRM system, returning results that match the given criteria.

    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["SEARCHEMAILS"],
            "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["SEARCHEMAILS"] + "\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["SEARCHEMAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/emails/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHEMAILS"],
        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=["crm.objects.contacts.read", "crm.objects.feedback_submissions.read"],
    )
)
async def get_feedback_submission_by_id(
    context: ToolContext,
    feedback_submission_id: Annotated[
        str,
        "The ID of the feedback submission to retrieve details for. This can be the internal object ID or a unique property value specified by `idProperty`.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If nonexistent, they will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return with their history of previous values. Ignored if not present on the object.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived results, false for active ones."
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List of properties to return in the response. Non-existent properties will be ignored.",
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property whose values are unique for the feedback submission object.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/feedback_submissions/{feedbackSubmissionId}_getById'.",  # noqa: E501
]:
    """Retrieve feedback submission details by ID.

    Use this tool to obtain details of a feedback submission using its ID. You can specify additional properties to control the details returned."""  # 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/crm/v3/objects/feedback_submissions/{feedbackSubmissionId}".format(  # noqa: UP032
            feedbackSubmissionId=feedback_submission_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_archived_only,
            "idProperty": unique_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=["crm.objects.contacts.read", "crm.objects.feedback_submissions.read"],
    )
)
async def search_feedback_submissions(
    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-/crm/v3/objects/feedback_submissions/search_doSearch'.",
]:
    """Search for feedback submissions in HubSpot CRM.

    This tool allows you to search for feedback submissions within the HubSpot CRM, helping you find specific feedback entries based on your search criteria.

    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["SEARCHFEEDBACKSUBMISSIONS"],
            "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["SEARCHFEEDBACKSUBMISSIONS"] + "\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["SEARCHFEEDBACKSUBMISSIONS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/feedback_submissions/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHFEEDBACKSUBMISSIONS"],
        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=["crm.objects.contacts.read", "crm.objects.feedback_submissions.read"],
    )
)
async def retrieve_feedback_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived feedback submission records.  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-/crm/v3/objects/feedback_submissions/batch/read_read'.",
]:
    """Retrieve feedback submission records by ID or custom properties.

    Use this tool to fetch feedback submission records from HubSpot CRM using either a record ID or a custom unique value property (`idProperty`).

    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["RETRIEVEFEEDBACKRECORDS"],
            "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["RETRIEVEFEEDBACKRECORDS"] + "\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["RETRIEVEFEEDBACKRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/feedback_submissions/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEFEEDBACKRECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.contacts.read", "crm.objects.feedback_submissions.read"],
    )
)
async def get_feedback_submissions(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "Comma separated list of object types to retrieve associated IDs for, like 'contacts' or 'companies'. Ignored if nonexistent.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None, "The token of the last read resource for fetching the next page of results."
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "List of properties to return for each feedback submission. Comma separated. Ignores non-existing properties.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names whose history of values should be returned. Properties not present will be ignored. Reduces the maximum number of submissions per request.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived feedback submissions; false to include both archived and active submissions.",  # noqa: E501
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/feedback_submissions_getPage'.",
]:
    """Retrieve a page of feedback submissions from the CRM.

    This tool retrieves a page of feedback submissions from HubSpot CRM. It allows control over the returned data using the 'properties' 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/crm/v3/objects/feedback_submissions",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.line_items.read"]))
async def get_fee_details(
    context: ToolContext,
    fee_identifier: Annotated[
        str,
        "The unique identifier for the fee object to retrieve. It can be the internal object ID or a value from a unique property specified by the `idProperty` parameter.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    only_return_archived_results: Annotated[
        bool | None, "Set to true to retrieve only archived results. False for non-archived."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history of previous values, specified as strings. Non-existent properties will be ignored.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "A list of properties to return for the fee object. Any non-existent properties will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "Specify the property name with unique values for the fee object."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/fees/{feeId}'."
]:
    """Retrieve information about a specific fee by ID.

    This tool is used to get detailed information about a fee object in HubSpot CRM using the fee's unique identifier. It can control the returned data with optional query parameters."""  # 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/crm/v3/objects/fees/{feeId}".format(feeId=fee_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived_results,
            "idProperty": unique_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=["crm.objects.line_items.write"]))
async def delete_fee_object(
    context: ToolContext,
    fee_id_to_delete: Annotated[
        str, "The unique identifier of the fee object to be deleted in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/fees/{feeId}'."
]:
    """Move a fee object to the recycling bin using its fee ID.

    Call this tool to delete a fee object in HubSpot CRM. Useful for removing unwanted or outdated fee records by providing the specific fee 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/crm/v3/objects/fees/{feeId}".format(feeId=fee_id_to_delete),  # noqa: UP032
        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=["crm.objects.line_items.write"]))
async def update_fee_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    fee_identifier: Annotated[
        str | None,
        "The ID or unique property value that identifies the fee object to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the unique property name to identify the object instead of the default ID.  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-/crm/v3/objects/fees/{feeId}'."
]:
    """Update specific details of a fee in the CRM.

    This tool performs a partial update of a fee record identified by its internal ID or another unique property. Use it to overwrite existing property values or clear them by passing an empty string. Be aware that read-only or non-existent properties will cause an error.

    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["UPDATEFEEDETAILS"],
            "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 fee_identifier:
        missing_params.append(("fee_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["UPDATEFEEDETAILS"] + "\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["UPDATEFEEDETAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/{feeId}".format(feeId=fee_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEFEEDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.line_items.write"]))
async def upsert_hubspot_fees(
    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-/crm/v3/objects/fees/batch/upsert'."
]:
    """Create or update fee records in HubSpot CRM.

    This tool should be called to create new fee records or update existing ones in HubSpot CRM. It uses a unique property value specified by the `idProperty` query parameter to identify records. Ideal for batch processing of fee information.

    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["UPSERTHUBSPOTFEES"],
            "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["UPSERTHUBSPOTFEES"] + "\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["UPSERTHUBSPOTFEES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTFEES"],
        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=["crm.objects.line_items.read"]))
async def get_crm_fees(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for. Ignored if associations don't exist.",  # noqa: E501
    ] = None,
    fee_properties_to_return: Annotated[
        list[str] | None,
        "List of properties to be returned in the response. If a property is not present, it will be ignored.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "The maximum number of fees to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token used to retrieve the next page of results. Use the token returned in `paging.next.after` from a previous response.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history. Reduces max number of fees per request.",
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/fees'."]:
    """Fetch a list of fees from the CRM.

    Retrieve a page of fee objects from the CRM, allowing control over returned properties through query parameters."""  # 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/crm/v3/objects/fees",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": fee_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.line_items.write"]))
async def create_fee_in_crm(
    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-/crm/v3/objects/fees'."]:
    """Create a fee in the CRM and receive the object's details.

    Call this tool to add a new fee in the CRM system using specified properties. It returns the newly created fee object along with its ID.

    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["CREATEFEEINCRM"],
            "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["CREATEFEEINCRM"] + "\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["CREATEFEEINCRM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEFEEINCRM"],
        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=["crm.objects.line_items.write"]))
async def create_batch_fees(
    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-/crm/v3/objects/fees/batch/create'."
]:
    """Create a batch of fees in HubSpot CRM.

    This tool allows the creation of multiple fees in a single batch within HubSpot CRM. It should be called when there is a need to add a set of new fees to the CRM system efficiently.

    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["CREATEBATCHFEES"],
            "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["CREATEBATCHFEES"] + "\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["CREATEBATCHFEES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHFEES"],
        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=["crm.objects.line_items.read"]))
async def search_fees_in_crm(
    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-/crm/v3/objects/fees/search'."
]:
    """Search for fees in HubSpot CRM.

    Use this tool to search for fees in the HubSpot CRM database. It should be called when you need to find specific fee records based on search criteria.

    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["SEARCHFEESINCRM"],
            "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["SEARCHFEESINCRM"] + "\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["SEARCHFEESINCRM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHFEESINCRM"],
        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=["crm.objects.line_items.write"]))
async def update_batch_fees(
    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-/crm/v3/objects/fees/batch/update'."
]:
    """Update multiple fees by internal ID or unique properties.

    Use this tool to update a batch of fees in HubSpot CRM by specifying internal IDs or unique property values. Call this tool when you need to make bulk updates to fee records.

    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["UPDATEBATCHFEES"],
            "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["UPDATEBATCHFEES"] + "\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["UPDATEBATCHFEES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHFEES"],
        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=["crm.objects.line_items.write"]))
async def archive_fees_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-/crm/v3/objects/fees/batch/archive'."
]:
    """Archives a batch of fees by their IDs in HubSpot CRM.

    Use this tool to archive multiple fee records in HubSpot CRM by providing their IDs. Ideal for managing outdated or unnecessary fee entries efficiently.

    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["ARCHIVEFEESBATCH"],
            "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["ARCHIVEFEESBATCH"] + "\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["ARCHIVEFEESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEFEESBATCH"],
        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=["crm.objects.line_items.read"]))
async def retrieve_fee_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived fee records. False to return active records.  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-/crm/v3/objects/fees/batch/read'."
]:
    """Retrieve fee records by ID or custom property.

    Use this tool to retrieve fee records from HubSpot CRM by specifying record IDs or using a custom unique value property. Useful for accessing detailed record information in batch.

    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["RETRIEVEFEERECORDS"],
            "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["RETRIEVEFEERECORDS"] + "\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["RETRIEVEFEERECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/fees/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEFEERECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.goals.write"]))
async def create_goal_targets_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-/crm/v3/objects/goal_targets/batch/create_create'.",
]:
    """Batch create multiple goal targets in HubSpot CRM.

    Use this tool to create multiple goal targets at once in HubSpot CRM. It is suited for managing and setting up several targets simultaneously, aiding in efficient CRM organization.

    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["CREATEGOALTARGETSBATCH"],
            "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["CREATEGOALTARGETSBATCH"] + "\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["CREATEGOALTARGETSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEGOALTARGETSBATCH"],
        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=["crm.objects.goals.read"]))
async def retrieve_goal_targets(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only the archived records.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/goal_targets/batch/read_read'.",
]:
    """Retrieve goal target records using record ID or custom value.

    Use this tool to retrieve goal target records from HubSpot CRM by providing either the record ID or a custom unique value property.

    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["RETRIEVEGOALTARGETS"],
            "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["RETRIEVEGOALTARGETS"] + "\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["RETRIEVEGOALTARGETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEGOALTARGETS"],
        params=remove_none_values({"archived": return_only_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=["crm.objects.goals.read"]))
async def get_goal_target_by_id(
    context: ToolContext,
    goal_target_id: Annotated[
        str,
        "The unique identifier for the goal target object. Can be the internal object ID or a unique property value specified by `idProperty`.",  # noqa: E501
    ],
    archived_results: Annotated[
        bool | None, "Set to true to return only archived results. Default is false."
    ] = False,
    associated_object_types: Annotated[
        list[str] | None,
        "Specify object types to retrieve associated IDs. If not present, they will be ignored. Use commas to separate multiple types.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated properties to return with their value histories. Ignored if properties are absent on the object.",  # noqa: E501
    ] = None,
    returned_properties: Annotated[
        list[str] | None, "List of properties to return. Ignored if not present on the object."
    ] = None,
    unique_property_name: Annotated[
        str | None, "The property name used as a unique identifier for the goal target object."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/goal_targets/{goalTargetId}_getById'.",
]:
    """Retrieve goal target object details using its ID.

    This tool retrieves details of a goal target object identified by `goalTargetId`. It can use either the default internal object ID or any unique property value specified by the `idProperty` query parameter. The `properties` query parameter can control what information is returned."""  # 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/crm/v3/objects/goal_targets/{goalTargetId}".format(  # noqa: UP032
            goalTargetId=goal_target_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": returned_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": archived_results,
            "idProperty": unique_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=["crm.objects.goals.write"]))
async def delete_goal_target(
    context: ToolContext,
    goal_target_id: Annotated[
        str,
        "The unique identifier for the goal target to be deleted. Required to specify which target to move to the recycling bin.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/goal_targets/{goalTargetId}_archive'.",
]:
    """Deletes a goal target by its ID to the recycling bin.

    Use this tool to delete a specific goal target by providing its unique ID. This action will move the target to the recycling bin, allowing for potential recovery."""  # 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/crm/v3/objects/goal_targets/{goalTargetId}".format(  # noqa: UP032
            goalTargetId=goal_target_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=["crm.objects.goals.write"]))
async def update_goal_target(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    goal_target_id: Annotated[
        str | None,
        "The internal ID of the goal target to update. Use this to specify which goal target object to modify.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for the goal target object used for identification or update.  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-/crm/v3/objects/goal_targets/{goalTargetId}_update'.",
]:
    """Update properties of a HubSpot goal target.

    Use this tool to perform a partial update on a HubSpot goal target object using its ID or a unique property. This is useful for editing specific properties of a goal target. Ensure only modifiable properties are included, as read-only or non-existent properties will cause errors. Clear properties by passing an empty string.

    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["UPDATEGOALTARGET"],
            "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 goal_target_id:
        missing_params.append(("goal_target_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["UPDATEGOALTARGET"] + "\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["UPDATEGOALTARGET"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/{goalTargetId}".format(  # noqa: UP032
            goalTargetId=goal_target_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEGOALTARGET"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.goals.read"]))
async def search_goal_targets(
    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-/crm/v3/objects/goal_targets/search_doSearch'.",
]:
    """Search for goal targets using specified criteria.

    This tool allows searching for goal targets within the HubSpot CRM based on specified criteria. Call this tool when you need to locate specific goal targets or retrieve a list based on certain parameters.

    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["SEARCHGOALTARGETS"],
            "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["SEARCHGOALTARGETS"] + "\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["SEARCHGOALTARGETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHGOALTARGETS"],
        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=["crm.objects.goals.write"]))
async def archive_goal_targets_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-/crm/v3/objects/goal_targets/batch/archive_archive'.",
]:
    """Archive multiple goal targets using their IDs in one batch.

    This tool allows users to archive multiple goal targets in HubSpot CRM by providing their IDs. It's useful for efficiently managing goal targets that are no longer needed.

    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["ARCHIVEGOALTARGETSBATCH"],
            "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["ARCHIVEGOALTARGETSBATCH"] + "\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["ARCHIVEGOALTARGETSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEGOALTARGETSBATCH"],
        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=["crm.objects.goals.write"]))
async def update_batch_goal_targets(
    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-/crm/v3/objects/goal_targets/batch/update_update'.",
]:
    """Update multiple goal targets in HubSpot CRM.

    Use this tool to update several goal targets simultaneously in HubSpot CRM using their internal IDs or unique property values.

    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["UPDATEBATCHGOALTARGETS"],
            "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["UPDATEBATCHGOALTARGETS"] + "\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["UPDATEBATCHGOALTARGETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHGOALTARGETS"],
        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=["crm.objects.goals.write"]))
async def upsert_goal_targets(
    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-/crm/v3/objects/goal_targets/batch/upsert_upsert'.",
]:
    """Create or update goal target records in HubSpot CRM.

    This tool allows creation or updating of goal target records in HubSpot CRM using a unique property for identification. Use it to ensure goal targets are up-to-date.

    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["UPSERTGOALTARGETS"],
            "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["UPSERTGOALTARGETS"] + "\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["UPSERTGOALTARGETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTGOALTARGETS"],
        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=["crm.objects.goals.read"]))
async def get_goal_targets(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for, ignored if non-existent.",
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "Token of the last successfully read item. Use it for fetching the next page of results.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated properties to return with their history of previous values. Reduces the max number of goals retrievable in one request.",  # noqa: E501
    ] = None,
    results_per_page_limit: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    returned_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return in the response. Ignored if absent on requested objects.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/goal_targets_getPage'."
]:
    """Retrieve a page of goal targets from HubSpot CRM.

    Use this tool to access a specific page of goal targets within HubSpot CRM. You can control the data returned by specifying the `properties` 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/crm/v3/objects/goal_targets",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page_limit,
            "after": paging_cursor_token,
            "properties": returned_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.goals.write"]))
async def create_goal_target(
    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-/crm/v3/objects/goal_targets_create'."
]:
    """Create a goal target in HubSpot CRM.

    Use this tool to create a new goal target in the HubSpot CRM system. It takes the properties for the goal target and returns a copy of the created object along with its ID.

    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["CREATEGOALTARGET"],
            "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["CREATEGOALTARGET"] + "\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["CREATEGOALTARGET"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/goal_targets",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEGOALTARGET"],
        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=["crm.objects.invoices.write"]))
async def create_batch_of_invoices(
    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-/crm/v3/objects/invoices/batch/create_create'.",
]:
    """Create a batch of invoices swiftly.

    Use this tool to create multiple invoices at once in HubSpot CRM when bulk billing is required.

    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.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["CREATEBATCHOFINVOICES"],
            "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["CREATEBATCHOFINVOICES"] + "\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["CREATEBATCHOFINVOICES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFINVOICES"],
        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=["crm.objects.invoices.read"]))
async def search_invoices(
    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-/crm/v3/objects/invoices/search_doSearch'.",
]:
    """Find invoices in the HubSpot CRM.

    Use this tool to search and retrieve invoices from the HubSpot CRM based on specific criteria.

    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.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["SEARCHINVOICES"],
            "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["SEARCHINVOICES"] + "\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["SEARCHINVOICES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHINVOICES"],
        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=["crm.objects.invoices.read"]))
async def retrieve_invoice_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only results that have been archived from HubSpot CRM.  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-/crm/v3/objects/invoices/batch/read_read'.",
]:
    """Retrieve invoice records by ID or custom property.

    Use this tool to obtain invoice records by specifying the record ID or using a custom unique value property. Ideal for accessing specific invoice data within the HubSpot CRM.

    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["RETRIEVEINVOICERECORDS"],
            "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["RETRIEVEINVOICERECORDS"] + "\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["RETRIEVEINVOICERECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEINVOICERECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.invoices.read"]))
async def get_invoice_by_id(
    context: ToolContext,
    invoice_identifier: Annotated[
        str,
        "The unique identifier for the invoice. This can be the internal object ID or any unique property value as specified by the `id_property`.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for, separated by commas. Ignored if associations don't exist.",  # noqa: E501
    ] = None,
    historical_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return along with their history of previous values.",
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "List of properties to be returned in the response for the specified invoice. Properties not present on the object will be ignored.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None,
        "Specify `True` to return only archived results. `False` returns both archived and non-archived results.",  # noqa: E501
    ] = False,
    unique_identifier_property: Annotated[
        str | None,
        "The name of a property whose values uniquely identify the invoice object. Specify this to use a unique property other than the internal object ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/invoices/{invoiceId}_getById'.",
]:
    """Retrieve invoice details by ID.

    Fetch invoice information using the unique identifier, `{invoiceId}`. This can include internal object ID or any unique property value specified by the `idProperty` 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/crm/v3/objects/invoices/{invoiceId}".format(  # noqa: UP032
            invoiceId=invoice_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": requested_properties,
            "propertiesWithHistory": historical_properties,
            "associations": associated_object_types,
            "archived": return_archived_only,
            "idProperty": unique_identifier_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=["crm.objects.invoices.write"]))
async def delete_invoice(
    context: ToolContext,
    invoice_identifier: Annotated[
        str, "The unique identifier for the invoice to be archived in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/invoices/{invoiceId}_archive'.",
]:
    """Archive an invoice by moving it to the recycling bin.

    Use this tool to archive an invoice in HubSpot CRM by moving it to the recycling bin. It is identified by the `{invoiceId}` 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/crm/v3/objects/invoices/{invoiceId}".format(  # noqa: UP032
            invoiceId=invoice_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=["crm.objects.invoices.write"]))
async def update_invoice_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    invoice_identifier: Annotated[
        str | None,
        "Unique identifier for the invoice, either the internal ID or specified unique property value, to update in HubSpot CRM.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Name of the unique property for identifying the invoice object.  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-/crm/v3/objects/invoices/{invoiceId}_update'.",
]:
    """Update invoice details in HubSpot CRM.

    Use this tool to perform a partial update of an invoice in HubSpot CRM. The invoice can be identified by its internal ID or a unique property. Only existing properties with provided values will be updated, and any read-only or non-existent properties will cause an error.

    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["UPDATEINVOICEDETAILS"],
            "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 invoice_identifier:
        missing_params.append(("invoice_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["UPDATEINVOICEDETAILS"] + "\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["UPDATEINVOICEDETAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/{invoiceId}".format(  # noqa: UP032
            invoiceId=invoice_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEINVOICEDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.invoices.write"]))
async def update_invoices_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-/crm/v3/objects/invoices/batch/update_update'.",
]:
    """Updates multiple invoices in the HubSpot CRM.

    This tool updates a batch of invoices in HubSpot CRM using either internal IDs or unique property values. It should be called when you need to modify multiple invoice records simultaneously.

    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["UPDATEINVOICESBATCH"],
            "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["UPDATEINVOICESBATCH"] + "\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["UPDATEINVOICESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEINVOICESBATCH"],
        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=["crm.objects.invoices.read"]))
async def retrieve_invoices(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types whose associated IDs should be retrieved. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "The maximum number of invoice results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token for the last successfully read resource to retrieve the next page of results.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List properties to return with their historical values. Reduced invoice limit per request.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived invoices; false for all invoices."
    ] = False,
    specified_properties: Annotated[
        list[str] | None,
        "List of invoice property names to return. Ignored if properties are not present on the objects.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/invoices_getPage'."
]:
    """Retrieve a page of invoices from HubSpot CRM.

    Use this tool to read a page of invoices from HubSpot CRM. You can control the return data by specifying desired properties through the query parameters."""  # 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/crm/v3/objects/invoices",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": specified_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.invoices.write"]))
async def create_hubspot_invoice(
    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-/crm/v3/objects/invoices_create'."
]:
    """Create an invoice in HubSpot CRM and retrieve its details.

    This tool creates a new invoice in HubSpot CRM using the provided properties and returns the created invoice's details, including its ID.

    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["CREATEHUBSPOTINVOICE"],
            "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["CREATEHUBSPOTINVOICE"] + "\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["CREATEHUBSPOTINVOICE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTINVOICE"],
        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=["crm.objects.invoices.write"]))
async def upsert_hubspot_invoices(
    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-/crm/v3/objects/invoices/batch/upsert_upsert'.",
]:
    """Create or update HubSpot invoice records in batch.

    This tool creates or updates HubSpot invoice records using a unique property identifier specified by the `idProperty` parameter. It should be called when you need to ensure invoice records are accurately reflected in HubSpot CRM.

    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["UPSERTHUBSPOTINVOICES"],
            "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["UPSERTHUBSPOTINVOICES"] + "\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["UPSERTHUBSPOTINVOICES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTINVOICES"],
        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=["crm.objects.invoices.write"]))
async def archive_invoices_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-/crm/v3/objects/invoices/batch/archive_archive'.",
]:
    """Archive a batch of invoices by their IDs.

    Use this tool to archive multiple invoices at once by providing their IDs. It should be called when there is a need to organize or hide invoices that are no longer active or needed.

    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["ARCHIVEINVOICESBATCH"],
            "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["ARCHIVEINVOICESBATCH"] + "\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["ARCHIVEINVOICESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/invoices/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEINVOICESBATCH"],
        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=["crm.objects.leads.read"]))
async def retrieve_lead_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Return only results that have been archived. Set to 'true' to filter by archived records.  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-/crm/v3/objects/leads/batch/read_read'."
]:
    """Retrieve lead records by ID or custom unique property.

    This tool fetches lead records from HubSpot CRM using specified record IDs or a custom unique value property if provided. It is useful for obtaining detailed information on leads based on specific identifiers.

    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["RETRIEVELEADRECORDS"],
            "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["RETRIEVELEADRECORDS"] + "\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["RETRIEVELEADRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVELEADRECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.leads.read"]))
async def search_crm_leads(
    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-/crm/v3/objects/leads/search_doSearch'."
]:
    """Search for leads in HubSpot CRM.

    This tool searches for leads in the HubSpot CRM. Use it when you want to query and retrieve lead information based on specific criteria.

    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["SEARCHCRMLEADS"],
            "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["SEARCHCRMLEADS"] + "\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["SEARCHCRMLEADS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCRMLEADS"],
        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=["crm.objects.leads.read"]))
async def get_hubspot_leads_page(
    context: ToolContext,
    archived_leads_only: Annotated[
        bool | None,
        "Return only leads that have been archived. Set to true to include only archived leads, false to exclude them.",  # noqa: E501
    ] = False,
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for; ignored if non-existent.",  # noqa: E501
    ] = None,
    lead_properties_to_return: Annotated[
        list[str] | None,
        "An array of the property names to include in the lead details response. Unavailable properties will be ignored.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None, "The cursor token to continue retrieving leads from where the last page ended."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties whose historical values will be returned. Reduce max leads per request.",  # noqa: E501
    ] = None,
    results_limit_per_page: Annotated[
        int | None, "Defines the maximum number of leads to display per page."
    ] = 10,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/leads_getPage'."
]:
    """Retrieve a page of leads from HubSpot CRM.

    This tool fetches a page of leads from the HubSpot CRM. It allows you to control the properties returned for the leads through query parameters."""  # 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/crm/v3/objects/leads",
        method="GET",
        params=remove_none_values({
            "limit": results_limit_per_page,
            "after": paging_cursor_token,
            "properties": lead_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": archived_leads_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=["crm.objects.leads.write"]))
async def create_lead_hubspot(
    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-/crm/v3/objects/leads_create'."
]:
    """Create a new lead in HubSpot CRM.

    Use this tool to create a new lead in HubSpot CRM by providing the necessary properties. It returns a copy of the lead object, including the ID.

    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["CREATELEADHUBSPOT"],
            "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["CREATELEADHUBSPOT"] + "\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["CREATELEADHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATELEADHUBSPOT"],
        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=["crm.objects.leads.write"]))
async def create_leads_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-/crm/v3/objects/leads/batch/create_create'.",
]:
    """Create a batch of new leads in HubSpot CRM.

    Use this tool to add multiple new leads to your HubSpot CRM account in one operation. Ideal for onboarding new leads efficiently.

    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["CREATELEADSBATCH"],
            "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["CREATELEADSBATCH"] + "\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["CREATELEADSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATELEADSBATCH"],
        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=["crm.objects.leads.write"]))
async def update_leads_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-/crm/v3/objects/leads/batch/update_update'.",
]:
    """Update multiple leads in a batch by ID or unique properties.

    Use this tool to update a set of leads in HubSpot CRM using their internal IDs or unique property values. This is useful for making bulk changes efficiently.

    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["UPDATELEADSBATCH"],
            "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["UPDATELEADSBATCH"] + "\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["UPDATELEADSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELEADSBATCH"],
        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=["crm.objects.leads.read"]))
async def get_lead_by_id(
    context: ToolContext,
    lead_identifier: Annotated[
        str,
        "The unique identifier for the lead. Typically the internal ID, or a unique property value if specified by `idProperty`.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for, with non-existent associations ignored.",  # noqa: E501
    ] = None,
    only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
    return_properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history of previous values. Comma-separated values should be used. Ignored if properties are not present.",  # noqa: E501
    ] = None,
    returned_properties: Annotated[
        list[str] | None,
        "A list of properties to be returned in the response. Only specified properties present on the lead will be included.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "Specifies a unique property name to identify the lead. Overrides default ID."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/leads/{leadsId}_getById'."
]:
    """Retrieve a lead by its unique identifier.

    This tool retrieves detailed information about a lead from HubSpot CRM using a unique identifier. It allows filtering of returned data using optional query parameters such as `idProperty` for specifying unique properties and `properties` for controlling which data is returned."""  # 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/crm/v3/objects/leads/{leadsId}".format(leadsId=lead_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": returned_properties,
            "propertiesWithHistory": return_properties_with_history,
            "associations": associated_object_types,
            "archived": only_archived_results,
            "idProperty": unique_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=["crm.objects.leads.write"]))
async def archive_hubspot_lead(
    context: ToolContext,
    lead_identifier: Annotated[
        str, "The unique identifier for the lead to be archived in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/leads/{leadsId}_archive'.",
]:
    """Archive a HubSpot CRM lead by identifier.

    Use this tool to move a HubSpot CRM lead, identified by `leadsId`, to the recycling bin. Ideal for managing and removing leads from active status."""  # 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/crm/v3/objects/leads/{leadsId}".format(leadsId=lead_identifier),  # noqa: UP032
        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=["crm.objects.leads.write"]))
async def update_lead_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    lead_identifier: Annotated[
        str | None,
        "The internal object ID or unique property value used to identify the lead in HubSpot CRM. Required for updating the lead details.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of a unique property to identify the lead, instead of using the internal ID.  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-/crm/v3/objects/leads/{leadsId}_update'."
]:
    """Update details of a specific lead in HubSpot CRM.

    Use this tool to perform a partial update to the details of a specific lead in the HubSpot CRM. You can specify the lead using their internal object ID or a unique property value. Only provided properties will be updated. This tool should be called when you need to update certain fields of a lead without altering others. Ensure that read-only and non-existent properties are not included, as these will result in an error.

    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["UPDATELEADDETAILS"],
            "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 lead_identifier:
        missing_params.append(("lead_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["UPDATELEADDETAILS"] + "\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["UPDATELEADDETAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/{leadsId}".format(leadsId=lead_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELEADDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.leads.write"]))
async def archive_leads_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-/crm/v3/objects/leads/batch/archive_archive'.",
]:
    """Archive a batch of leads by ID in HubSpot CRM.

    Use this tool to archive multiple leads in HubSpot CRM by providing their IDs. Useful for managing large numbers of leads efficiently.

    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["ARCHIVELEADSBATCH"],
            "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["ARCHIVELEADSBATCH"] + "\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["ARCHIVELEADSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/leads/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVELEADSBATCH"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_association_limit_records(
    context: ToolContext,
    source_object_type_id: Annotated[
        str, "Specifies the ID of the source object type to check association limits from."
    ],
    to_object_type_id: Annotated[
        str, "The ID of the target object type for the association limit query."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/limits/associations/records/{fromObjectTypeId}/{toObjectTypeId}'.",  # noqa: E501
]:
    """Fetch records near association limits between two objects.

    Use this tool to retrieve records that are close to or have reached the association limits between specified objects in HubSpot CRM. This is useful for monitoring and managing object relationships within the CRM 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/crm/v3/limits/associations/records/{fromObjectTypeId}/{toObjectTypeId}".format(  # noqa: UP032
            fromObjectTypeId=source_object_type_id, toObjectTypeId=to_object_type_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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_custom_association_labels_limits(
    context: ToolContext,
    source_object_type_id: Annotated[
        str | None,
        "The unique identifier for the source object type. It specifies which object type the association is coming from in the CRM.",  # noqa: E501
    ] = None,
    target_object_type_id: Annotated[
        str | None,
        "The ID of the target object type to which the association label applies. Specify the target entity in HubSpot CRM.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/associations/labels'."
]:
    """Get limits and usage for custom association labels in HubSpot CRM.

    This tool retrieves limits and current usage information for custom association labels within the HubSpot CRM environment. It should be called when you need to understand the constraints or current utilization of these labels."""  # 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/crm/v3/limits/associations/labels",
        method="GET",
        params=remove_none_values({
            "fromObjectTypeId": source_object_type_id,
            "toObjectTypeId": target_object_type_id,
        }),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def fetch_association_limit_objects(
    context: ToolContext,
    from_object_type_id: Annotated[
        str,
        "Identifier for the 'from' object type whose records' association limits are being queried.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/limits/associations/records/{fromObjectTypeId}/to'.",  # noqa: E501
]:
    """Fetch objects approaching association limits for a specified type.

    Use this tool to retrieve objects where the specified 'from' object type has records that are nearing or have reached their association limits in HubSpot CRM."""  # 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/crm/v3/limits/associations/records/{fromObjectTypeId}/to".format(  # noqa: UP032
            fromObjectTypeId=from_object_type_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=[
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.custom.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.custom.read",
            "crm.schemas.custom.read",
            "crm.objects.custom.write",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.custom.sensitive.read.v2",
        ],
    )
)
async def get_custom_object_limits(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/custom-object-types'."
]:
    """Retrieve limits and usage for HubSpot custom object schemas.

    This tool is used to get the limits and usage data for custom object schemas in HubSpot CRM. It's useful for monitoring and managing custom object types and understanding their current usage."""  # 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/crm/v3/limits/custom-object-types",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_custom_property_limits(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/custom-properties'."
]:
    """Retrieve limits and usage for custom properties per object.

    Use this tool to access the limits and usage statistics for custom properties categorized by object in HubSpot CRM."""  # 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/crm/v3/limits/custom-properties",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def retrieve_limit_approaching_records(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/associations/records/from'."
]:
    """Retrieve objects nearing or at HubSpot CRM association limits.

    This tool is called to get objects from HubSpot CRM that have records close to or at their association limits. It helps in monitoring and managing association constraints."""  # 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/crm/v3/limits/associations/records/from",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_hubspot_crm_limits_records(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/records'."]:
    """Retrieve limits and usage for records in HubSpot CRM.

    Use this tool to obtain detailed information about the limits and current usage of various objects within the HubSpot CRM records."""  # 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/crm/v3/limits/records",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def pipeline_limits_usage(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/pipelines'."]:
    """Retrieve limits and usage for HubSpot CRM pipelines.

    Call this tool to obtain information about the limits and current usage of pipelines in HubSpot CRM. Useful for understanding capacity and utilization of your CRM pipelines."""  # 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/crm/v3/limits/pipelines",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def get_calculated_properties_limits(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/limits/calculated-properties'."
]:
    """Get limits and usage for calculated properties in HubSpot CRM.

    Call this tool to retrieve overall limits and per object usage for calculated properties in HubSpot CRM."""  # 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/crm/v3/limits/calculated-properties",
        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=["crm.objects.line_items.read"]))
async def retrieve_line_items_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs. Nonexistent associations are ignored.",
    ] = None,
    include_properties: Annotated[
        list[str] | None,
        "List the properties to include in the response, separated by commas. Ignored if not present.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Sets the maximum number of line items to display per page."
    ] = 10,
    pagination_cursor_after: Annotated[
        str | None, "The cursor token from the last page to retrieve the next set of results."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names to return with their value history. This reduces the max line items per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None,
        "Set to true to return only archived results. False returns non-archived results.",
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/line_items_getPage'."
]:
    """Retrieve a page of line items from HubSpot CRM.

    Use this tool to read a specific page of line items from HubSpot CRM. You can control the details returned using the `properties` query parameter to tailor the response to your needs."""  # 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/crm/v3/objects/line_items",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": pagination_cursor_after,
            "properties": include_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.line_items.write"]))
async def create_hubspot_line_item(
    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-/crm/v3/objects/line_items_create'."
]:
    """Create a new line item in HubSpot CRM.

    This tool creates a line item in HubSpot CRM with specified properties and returns the complete object, including its ID. Use this when you need to add a line item to HubSpot.

    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["CREATEHUBSPOTLINEITEM"],
            "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["CREATEHUBSPOTLINEITEM"] + "\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["CREATEHUBSPOTLINEITEM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTLINEITEM"],
        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=["crm.objects.line_items.read"]))
async def get_line_item_details(
    context: ToolContext,
    line_item_id: Annotated[
        str,
        "The unique ID or property value of the line item to retrieve. This identifies the specific line item in HubSpot CRM.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Comma separated list of object types to retrieve associated IDs for. Non-existent associations are ignored.",  # noqa: E501
    ] = None,
    only_return_archived: Annotated[
        bool | None, "Set to True to return only archived results."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their value history. Ignored if not present on the object.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return. Ignored if not present on the object.",
    ] = None,
    unique_identifier_property: Annotated[
        str | None, "Specifies a unique property name to identify the line item in HubSpot CRM."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/line_items/{lineItemId}_getById'.",
]:
    """Retrieve details of a line item by its ID.

    This tool fetches details of a specific line item from HubSpot CRM using its internal ID or any unique property value. Use this tool to access information about a particular line item, specifying any desired properties to control the response."""  # 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/crm/v3/objects/line_items/{lineItemId}".format(  # noqa: UP032
            lineItemId=line_item_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived,
            "idProperty": unique_identifier_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=["crm.objects.line_items.write"]))
async def delete_line_item(
    context: ToolContext,
    line_item_id: Annotated[
        str, "The unique identifier of the line item to be archived or deleted."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/line_items/{lineItemId}_archive'.",
]:
    """Moves a specified line item to the recycling bin.

    This tool is used to delete or archive a line item in HubSpot CRM by moving it to the recycling bin. It should be called when a user wants to remove a line item identified by its `lineItemId`."""  # 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/crm/v3/objects/line_items/{lineItemId}".format(  # noqa: UP032
            lineItemId=line_item_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=["crm.objects.line_items.write"]))
async def update_line_item(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    line_item_id: Annotated[
        str | None,
        "The internal ID of the line item to update. This ID identifies the object to be modified.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify a property with unique values to identify the line item instead of using the internal ID.  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-/crm/v3/objects/line_items/{lineItemId}_update'.",
]:
    """Update properties of a CRM line item using its ID.

    Use this tool to perform a partial update of a CRM line item identified by its internal ID or a unique property value. Only provided properties will be updated, and passing an empty string can clear property values. Make sure not to include read-only or non-existent properties as they will result in errors.

    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["UPDATELINEITEM"],
            "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 line_item_id:
        missing_params.append(("line_item_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["UPDATELINEITEM"] + "\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["UPDATELINEITEM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/{lineItemId}".format(  # noqa: UP032
            lineItemId=line_item_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELINEITEM"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.line_items.write"]))
async def upsert_line_items_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-/crm/v3/objects/line_items/batch/upsert_upsert'.",
]:
    """Batch create or update line items by unique ID.

    This tool creates or updates line item records in batches using a unique property to identify each item, as specified by the `idProperty` query parameter.

    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["UPSERTLINEITEMSBATCH"],
            "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["UPSERTLINEITEMSBATCH"] + "\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["UPSERTLINEITEMSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTLINEITEMSBATCH"],
        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=["crm.objects.line_items.write"]))
async def create_line_items_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-/crm/v3/objects/line_items/batch/create_create'.",
]:
    """Create a batch of line items in HubSpot CRM.

    Use this tool to create multiple line items at once in HubSpot CRM. Ideal for bulk operations where several line items need to be added simultaneously to the 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["CREATELINEITEMSBATCH"],
            "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["CREATELINEITEMSBATCH"] + "\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["CREATELINEITEMSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATELINEITEMSBATCH"],
        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=["crm.objects.line_items.write"]))
async def update_line_items_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-/crm/v3/objects/line_items/batch/update_update'.",
]:
    """Update multiple line items in CRM using internal IDs or unique properties.

    This tool updates a batch of line items in HubSpot CRM. It's useful when needing to modify multiple line items at once using their internal IDs or unique property values.

    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["UPDATELINEITEMSBATCH"],
            "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["UPDATELINEITEMSBATCH"] + "\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["UPDATELINEITEMSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATELINEITEMSBATCH"],
        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=["crm.objects.line_items.read"]))
async def search_line_items(
    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-/crm/v3/objects/line_items/search_doSearch'.",
]:
    """Search for line items in HubSpot CRM.

    Use this tool to search for line items within HubSpot CRM. It should be called when you need to retrieve specific line items based on search criteria.

    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["SEARCHLINEITEMS"],
            "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["SEARCHLINEITEMS"] + "\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["SEARCHLINEITEMS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHLINEITEMS"],
        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=["crm.objects.line_items.read"]))
async def retrieve_batch_line_items(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    retrieve_only_archived: Annotated[
        bool | None,
        "Set to true to retrieve only archived line items.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/line_items/batch/read_read'.",
]:
    """Retrieve batch line item records by ID or custom property.

    Use this tool to retrieve multiple line item records from HubSpot CRM using their IDs or a custom unique property. This is helpful for accessing specific sets of records without manually querying each one individually.

    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["RETRIEVEBATCHLINEITEMS"],
            "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["RETRIEVEBATCHLINEITEMS"] + "\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["RETRIEVEBATCHLINEITEMS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEBATCHLINEITEMS"],
        params=remove_none_values({"archived": retrieve_only_archived}),
        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=["crm.objects.line_items.write"]))
async def archive_line_items_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-/crm/v3/objects/line_items/batch/archive_archive'.",
]:
    """Archive a batch of line items in HubSpot CRM.

    Use this tool to archive multiple line items at once by their IDs within HubSpot CRM. Ideal for managing and organizing CRM data efficiently.

    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["ARCHIVELINEITEMSBATCH"],
            "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["ARCHIVELINEITEMSBATCH"] + "\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["ARCHIVELINEITEMSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/line_items/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVELINEITEMSBATCH"],
        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=["crm.objects.listings.read"]))
async def list_crm_entries(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for, such as 'contacts' or 'deals'. Ignored if not existent.",  # noqa: E501
    ] = None,
    include_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. False to exclude archived entries.",
    ] = False,
    include_properties_with_history: Annotated[
        list[str] | None,
        "List the properties whose values along with their history should be returned. Reduces the number of listings per request.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None,
        "The maximum number of CRM listing results to display per page. This controls pagination size.",  # noqa: E501
    ] = 10,
    paging_cursor_token: Annotated[
        str | None, "The cursor token for fetching the next page of CRM listings."
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "Comma-separated list of properties to include in the response. Non-existent properties will be ignored.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-420'."]:
    """Retrieve a page of CRM listings with specified properties.

    This tool fetches a page of CRM listings from HubSpot, allowing the user to control which properties are returned via query parameters. Use this to obtain customer records and details from the CRM."""  # 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/crm/v3/objects/0-420",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": properties_to_return,
            "propertiesWithHistory": include_properties_with_history,
            "associations": associated_object_types,
            "archived": include_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=["crm.objects.listings.write"]))
async def create_hubspot_listing(
    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-/crm/v3/objects/0-420'."]:
    """Create a HubSpot CRM listing and get the object details.

    Use this tool to create a listing in HubSpot CRM with specified properties. It returns the newly created object's details, including its ID.

    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["CREATEHUBSPOTLISTING"],
            "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["CREATEHUBSPOTLISTING"] + "\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["CREATEHUBSPOTLISTING"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTLISTING"],
        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=["crm.objects.listings.write"]))
async def archive_multiple_listings(
    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-/crm/v3/objects/0-420/batch/archive'."
]:
    """Archive multiple listings using their IDs.

    This tool archives multiple listings in HubSpot CRM by providing their IDs. It should be called when you need to bulk archive specific listings efficiently.

    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["ARCHIVEMULTIPLELISTINGS"],
            "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["ARCHIVEMULTIPLELISTINGS"] + "\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["ARCHIVEMULTIPLELISTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEMULTIPLELISTINGS"],
        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=["crm.objects.listings.write"]))
async def update_multiple_crm_listings(
    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-/crm/v3/objects/0-420/batch/update'."
]:
    """Update multiple CRM listings using internal IDs or unique properties.

    This tool updates multiple listings in the CRM using their internal IDs or unique property values. It should be called when there is a need to modify several records in bulk within the CRM 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["UPDATEMULTIPLECRMLISTINGS"],
            "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["UPDATEMULTIPLECRMLISTINGS"] + "\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["UPDATEMULTIPLECRMLISTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLECRMLISTINGS"],
        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=["crm.objects.listings.read"]))
async def fetch_hubspot_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_records_only: Annotated[
        bool | None,
        "Set to true to return only HubSpot CRM records that have been archived.  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-/crm/v3/objects/0-420/batch/read'."
]:
    """Retrieve HubSpot CRM records by ID or custom property.

    This tool retrieves records from HubSpot CRM using either a record ID or a custom unique value property. It should be called when you need to access specific CRM records based on these identifiers.

    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["FETCHHUBSPOTRECORDS"],
            "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["FETCHHUBSPOTRECORDS"] + "\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["FETCHHUBSPOTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["FETCHHUBSPOTRECORDS"],
        params=remove_none_values({"archived": return_archived_records_only}),
        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=["crm.objects.listings.write"]))
async def create_multiple_listings(
    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-/crm/v3/objects/0-420/batch/create'."
]:
    """Create multiple listings in a single request.

    Use this tool to add several listings to the CRM in one operation. Ideal for bulk uploads or extensive data entry 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["CREATEMULTIPLELISTINGS"],
            "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["CREATEMULTIPLELISTINGS"] + "\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["CREATEMULTIPLELISTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEMULTIPLELISTINGS"],
        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=["crm.objects.listings.write"]))
async def create_or_update_batch_records(
    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-/crm/v3/objects/0-420/batch/upsert'."
]:
    """Create or update CRM records in batches.

    Use this tool to create or update records in the CRM identified by a unique property value. It allows handling multiple records in a single batch operation, specified by the `idProperty`.

    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["CREATEORUPDATEBATCHRECORDS"],
            "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["CREATEORUPDATEBATCHRECORDS"]
                + "\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["CREATEORUPDATEBATCHRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATEBATCHRECORDS"],
        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=["crm.objects.listings.read"]))
async def search_hubspot_listings(
    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-/crm/v3/objects/0-420/search'."
]:
    """Search listings in HubSpot CRM using filters and properties.

    Use this tool to execute a search query in HubSpot CRM to find listings. You can specify various filters and properties to refine your search results.

    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["SEARCHHUBSPOTLISTINGS"],
            "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["SEARCHHUBSPOTLISTINGS"] + "\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["SEARCHHUBSPOTLISTINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTLISTINGS"],
        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=["crm.objects.listings.read"]))
async def get_listing_details(
    context: ToolContext,
    unique_listing_id: Annotated[
        str, "The unique identifier for the listing to be retrieved in HubSpot CRM."
    ],
    properties_to_return: Annotated[
        list[str] | None,
        "A list of properties to be included in the response. Non-existent properties will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None, "An array of property names to return along with their historical values."
    ] = None,
    retrieve_associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Comma separated. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Use false to include non-archived as well.",
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "The name of a property with unique values to identify the object in HubSpot CRM.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-420/{listingId}'."
]:
    """Retrieve details of a listing by its ID.

    Retrieve information about a listing in HubSpot CRM using a unique listing ID or any specified unique property. Allows control of returned details via query parameters."""  # 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/crm/v3/objects/0-420/{listingId}".format(  # noqa: UP032
            listingId=unique_listing_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_types,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.listings.write"]))
async def move_listing_to_recycle_bin(
    context: ToolContext,
    listing_id: Annotated[
        str, "The unique identifier of the listing to be moved to the recycling bin in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/0-420/{listingId}'."
]:
    """Move a listing to the recycling bin by ID.

    Use this tool to move a specific listing identified by its ID to the recycling bin in HubSpot CRM."""  # 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/crm/v3/objects/0-420/{listingId}".format(listingId=listing_id),  # noqa: UP032
        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=["crm.objects.listings.write"]))
async def hubspot_update_listing(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    listing_id: Annotated[
        str | None,
        "The unique identifier of the listing to update in HubSpot.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for this object, used for identification.  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-/crm/v3/objects/0-420/{listingId}'."
]:
    """Update specific details of a HubSpot listing.

    This tool performs a partial update of a HubSpot listing identified by `listingId` or a unique property specified by `idProperty`. It overwrites provided property values, with errors for read-only or non-existent properties. Clear values by passing an empty string.

    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["HUBSPOTUPDATELISTING"],
            "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 listing_id:
        missing_params.append(("listing_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["HUBSPOTUPDATELISTING"] + "\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["HUBSPOTUPDATELISTING"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-420/{listingId}".format(listingId=listing_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["HUBSPOTUPDATELISTING"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def update_list_name(
    context: ToolContext,
    list_id: Annotated[str, "The unique ILS ID of the list to update."],
    include_filter_branch_definition: Annotated[
        bool | None,
        "Set to true to include filter branch definitions in the response list definition, or false to exclude them.",  # noqa: E501
    ] = False,
    new_list_name: Annotated[
        str | None,
        "The new name for the CRM list. It must be globally unique relative to other public lists.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/{listId}/update-list-name_updateName'.",
]:
    """Update the name of a CRM list in HubSpot.

    Call this tool to update the name of a CRM list in HubSpot, ensuring the new name is globally unique."""  # 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/crm/v3/lists/{listId}/update-list-name".format(listId=list_id),  # noqa: UP032
        method="PUT",
        params=remove_none_values({
            "listName": new_list_name,
            "includeFilters": include_filter_branch_definition,
        }),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def update_list_memberships(
    context: ToolContext,
    list_identifier: Annotated[str, "The unique ILS ID of the MANUAL or SNAPSHOT list to update."],
    record_ids_to_add: Annotated[
        list[str],
        "An array of record IDs to be added to the specified list. Ensure these records are already created in the system.",  # noqa: E501
    ],
    record_ids_to_remove: Annotated[
        list[str], "An array of record IDs to remove from the list. Each ID should be a string."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/{listId}/memberships/add-and-remove_addAndRemove'.",  # noqa: E501
]:
    """Add or remove records from a manual or snapshot list.

    This tool is used to add and/or remove records from a manual or snapshot list in the HubSpot CRM. It facilitates managing list memberships by updating existing records according to specified changes."""  # noqa: E501
    request_data: Any = {
        "recordIdsToRemove": record_ids_to_remove,
        "recordIdsToAdd": record_ids_to_add,
    }
    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/crm/v3/lists/{listId}/memberships/add-and-remove".format(  # noqa: UP032
            listId=list_identifier
        ),
        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=["crm.lists.read"]))
async def fetch_hubspot_list_by_id(
    context: ToolContext,
    list_id: Annotated[str, "The ILS ID of the HubSpot CRM list to fetch."],
    include_filter_definitions: Annotated[
        bool | None,
        "Include filter branch definitions in the response. Defaults to false, meaning filter definitions are not included.",  # noqa: E501
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/lists/{listId}_getById'."
]:
    """Fetch a single HubSpot CRM list using its ILS list 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/crm/v3/lists/{listId}".format(listId=list_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({"includeFilters": include_filter_definitions}),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def delete_list(
    context: ToolContext,
    list_id_to_delete: Annotated[
        str,
        "The ILS ID of the CRM list to delete. Ensure the ID is correct to avoid unintentional deletion.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/lists/{listId}_remove'."
]:
    """Delete a specified CRM list by its ID.

    Use this tool to delete a CRM list using its ID. Deleted lists can be restored within 90 days, after which they'll be permanently removed."""  # 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/crm/v3/lists/{listId}".format(listId=list_id_to_delete),  # noqa: UP032
        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=["crm.lists.read"]))
async def retrieve_conversion_details(
    context: ToolContext,
    list_id: Annotated[
        str, "The ID of the list for which you want to retrieve conversion details."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/{listId}/schedule-conversion'.",
]:
    """Retrieve conversion details for a specific list in HubSpot CRM.

    Use this tool to check for upcoming conversions or to get details of past conversions for a list."""  # 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/crm/v3/lists/{listId}/schedule-conversion".format(  # noqa: UP032
            listId=list_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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def schedule_list_conversion(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    list_id: Annotated[
        str | None,
        "The ID of the list you want to schedule the conversion for.  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-/crm/v3/lists/{listId}/schedule-conversion'.",
]:
    """Schedule or update the conversion of an active list to static.

    Use this tool to schedule the conversion of an active list into a static list, or to update an already scheduled conversion. This can be scheduled for a specific date or based on activity in HubSpot CRM.

    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["SCHEDULELISTCONVERSION"],
            "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 list_id:
        missing_params.append(("list_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["SCHEDULELISTCONVERSION"] + "\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["SCHEDULELISTCONVERSION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/lists/{listId}/schedule-conversion".format(  # noqa: UP032
            listId=list_id
        ),
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SCHEDULELISTCONVERSION"],
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def delete_scheduled_conversion(
    context: ToolContext,
    list_id: Annotated[
        str, "The ID of the list for which you want to cancel the scheduled conversion."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/lists/{listId}/schedule-conversion'.",
]:
    """Delete a scheduled conversion for a specific list.

    This tool deletes an existing scheduled conversion for a specified list in the HubSpot CRM. It should be called when you need to cancel a conversion schedule, and it returns a confirmation of the deletion."""  # 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/crm/v3/lists/{listId}/schedule-conversion".format(  # noqa: UP032
            listId=list_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=["crm.lists.read"]))
async def search_hubspot_lists(
    context: ToolContext,
    filter_by_list_ids: Annotated[
        list[str] | None,
        "An array of list IDs to filter search results. If not provided or empty, no filter is applied.",  # noqa: E501
    ] = None,
    filter_by_processing_types: Annotated[
        list[str] | None,
        "List of processing types to filter results. Valid values: 'MANUAL', 'SNAPSHOT', 'DYNAMIC'. If omitted, no filtering by processing type is applied.",  # noqa: E501
    ] = None,
    include_additional_list_properties: Annotated[
        list[str] | None,
        "Specify additional list properties to include in the response. Defaults fetch standard properties like `hs_list_size` and others.",  # noqa: E501
    ] = None,
    number_of_lists_to_return: Annotated[
        int | None,
        "The number of lists to include in the response. Defaults to 20 if not provided, with a maximum of 500.",  # noqa: E501
    ] = None,
    search_query: Annotated[
        str | None, "The term to search for lists by name. Returns all lists if empty."
    ] = None,
    sort_order: Annotated[
        str | None,
        "Specify the order in which the lists should be sorted. Acceptable values could be 'asc' for ascending or 'desc' for descending order.",  # noqa: E501
    ] = None,
    start_offset: Annotated[
        int | None,
        "The starting point for pagination of list results. Defaults to `0` if not provided.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/lists/search_doSearch'."
]:
    """Search HubSpot CRM lists by name or page through all lists.

    This tool allows searching for specific lists within the HubSpot CRM by list name. Alternatively, you can page through all lists by providing an empty query. It's useful for retrieving specific list data or browsing all available lists."""  # noqa: E501
    request_data: Any = {
        "listIds": filter_by_list_ids,
        "offset": start_offset,
        "query": search_query,
        "count": number_of_lists_to_return,
        "processingTypes": filter_by_processing_types,
        "additionalProperties": include_additional_list_properties,
        "sort": sort_order,
    }
    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/crm/v3/lists/search",
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def move_list_to_folder(
    context: ToolContext,
    list_id: Annotated[
        str,
        "The ID of the list you want to move. It should be a valid string representing the list in HubSpot CRM.",  # noqa: E501
    ],
    target_folder_id: Annotated[
        str, "The ID of the folder to move the list to. Use '0' for the root folder."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'put-/crm/v3/lists/folders/move-list_moveList'."
]:
    """Move a CRM list to a specified folder.

    Use this tool to move a specific list within HubSpot CRM to a desired folder, organizing lists efficiently."""  # noqa: E501
    request_data: Any = {"listId": list_id, "newFolderId": target_folder_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/crm/v3/lists/folders/move-list",
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def add_to_hubspot_crm_list(
    context: ToolContext,
    list_id: Annotated[str, "The ILS ID of the MANUAL or SNAPSHOT list to add records."],
    record_ids_to_add: Annotated[
        list[str], "An array of strings representing the IDs of the records to add to the list."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/{listId}/memberships/add_add'.",
]:
    """Add records to a specified HubSpot CRM list.

    Use this tool to add records to a specific HubSpot CRM list. Only works for lists with a processing type of MANUAL or SNAPSHOT. Records that don't exist or are already members will be ignored."""  # noqa: E501
    request_data: Any = record_ids_to_add
    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/crm/v3/lists/{listId}/memberships/add".format(listId=list_id),  # noqa: UP032
        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=["crm.lists.read"]))
async def fetch_list_by_name(
    context: ToolContext,
    list_name: Annotated[str, "The name of the list to fetch. This is not case sensitive."],
    object_type_id: Annotated[
        str, "The object type ID for the list. Example: `0-1` for `CONTACT`."
    ],
    include_filters: Annotated[
        bool | None,
        "Set to true to include filter branch definitions in the response. By default, filters are not included.",  # noqa: E501
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/object-type-id/{objectTypeId}/name/{listName}_getByName'.",  # noqa: E501
]:
    """Fetch details of a list by its name and object type.

    Use this tool to obtain the information of a specific list in HubSpot CRM by providing the list name and object type 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/crm/v3/lists/object-type-id/{objectTypeId}/name/{listName}".format(  # noqa: UP032
            listName=list_name, objectTypeId=object_type_id
        ),
        method="GET",
        params=remove_none_values({"includeFilters": include_filters}),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def move_folder_in_hubspot(
    context: ToolContext,
    folder_id_to_move: Annotated[
        str, "The ID of the folder you want to move to a new location in HubSpot CRM."
    ],
    target_parent_folder_id: Annotated[
        str, "The ID of the target parent folder to which the current folder will be moved."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/folders/{folderId}/move/{newParentFolderId}_move'.",  # noqa: E501
]:
    """Move a folder to a new parent in HubSpot CRM.

    This tool moves a folder to a new parent location in HubSpot CRM, updating the folder's parent ID to the specified new ID. Use this when you need to reorganize folder structures within the CRM."""  # 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/crm/v3/lists/folders/{folderId}/move/{newParentFolderId}".format(  # noqa: UP032
            folderId=folder_id_to_move, newParentFolderId=target_parent_folder_id
        ),
        method="PUT",
        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=["crm.lists.read"]))
async def translate_legacy_to_new_list_id(
    context: ToolContext,
    legacy_list_id: Annotated[
        str | None, "The legacy list ID from the lists v1 API to be translated to the new format."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/idmapping_translateLegacyListIdToListId'.",
]:
    """Translate legacy list ID to the new list ID format.

    This tool translates a legacy list ID to the new list ID format using HubSpot CRM's temporary API. It is useful for mapping old list IDs to new ones before the API expires on May 30th, 2025."""  # 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/crm/v3/lists/idmapping",
        method="GET",
        params=remove_none_values({"legacyListId": legacy_list_id}),
        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=["crm.lists.read"]))
async def translate_legacy_list_ids_batch(
    context: ToolContext,
    legacy_list_ids: Annotated[
        list[str],
        "An array of legacy list IDs to be translated to new IDs, supporting up to 10,000 strings.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/crm/v3/lists/idmapping_translateLegacyListIdToListIdBatch'.",  # noqa: E501
]:
    """Translate legacy list IDs to new list IDs in batch.

    Use this tool to convert a large number (up to 10,000) of legacy list IDs to the current list ID format. This tool is essential for data migration purposes and is available until May 30th, 2025."""  # noqa: E501
    request_data: Any = legacy_list_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/crm/v3/lists/idmapping",
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def restore_deleted_list(
    context: ToolContext,
    list_id_to_restore: Annotated[
        str, "The ILS ID of the list to restore. Use this to specify which deleted list to recover."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'put-/crm/v3/lists/{listId}/restore_restore'."
]:
    """Restore a previously deleted HubSpot CRM list.

    This tool restores a previously deleted list in HubSpot CRM using the list ID. Lists can be restored up to 90 days after deletion. Use this when you need to recover a list that was mistakenly deleted."""  # 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/crm/v3/lists/{listId}/restore".format(  # noqa: UP032
            listId=list_id_to_restore
        ),
        method="PUT",
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def rename_crm_folder(
    context: ToolContext,
    folder_id: Annotated[str, "The ID of the folder you want to rename in HubSpot CRM."],
    new_folder_name: Annotated[
        str | None,
        "The new name to assign to the folder. It should be a string representing the desired folder name in HubSpot CRM.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/folders/{folderId}/rename_rename'.",
]:
    """Rename a folder in HubSpot CRM by its folder ID.

    Use this tool to rename a specific folder in HubSpot CRM using its folder ID. Useful for organizing or updating folder names within your CRM 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/crm/v3/lists/folders/{folderId}/rename".format(  # noqa: UP032
            folderId=folder_id
        ),
        method="PUT",
        params=remove_none_values({"newFolderName": new_folder_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=["crm.lists.read"]))
async def fetch_list_memberships_ordered(
    context: ToolContext,
    list_id: Annotated[str, "The unique ILS ID of the list to retrieve memberships from."],
    after_paging_offset_token: Annotated[
        str | None,
        "The token for the page that comes after the previously requested records, sorted in ascending order. Takes precedence over 'before'.",  # noqa: E501
    ] = None,
    before_offset_token: Annotated[
        str | None,
        "The paging offset token to retrieve records preceding the specified page, sorted in descending order.",  # noqa: E501
    ] = None,
    record_limit: Annotated[
        int | None, "Specify the number of records to return, with a maximum limit of 250."
    ] = 100,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/{listId}/memberships/join-order_getPageOrderedByAddedToListDate'.",  # noqa: E501
]:
    """Fetch list memberships ordered by addition date.

    Fetch the memberships of a HubSpot CRM list, ordered by the date they were added. Useful for retrieving member records in an ascending order based on addition time, or in descending order if a 'before' offset is used."""  # 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/crm/v3/lists/{listId}/memberships/join-order".format(  # noqa: UP032
            listId=list_id
        ),
        method="GET",
        params=remove_none_values({
            "after": after_paging_offset_token,
            "before": before_offset_token,
            "limit": record_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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def add_all_from_source_list_to_destination_list(
    context: ToolContext,
    destination_list_id: Annotated[
        str,
        "The ILS ID of the MANUAL or SNAPSHOT destination list to which the source list records are added.",  # noqa: E501
    ],
    source_list_id: Annotated[
        str, "The ILS ID of the source list from which records are added to the destination list."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/{listId}/memberships/add-from/{sourceListId}_addAllFromList'.",  # noqa: E501
]:
    """Add records from a source list to a destination list in HubSpot.

    This tool transfers all records from a specified source list to a destination list in HubSpot CRM, ignoring duplicates. Suitable for destination lists with manual or snapshot processing and source lists with fewer than 100,000 memberships."""  # 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/crm/v3/lists/{listId}/memberships/add-from/{sourceListId}".format(  # noqa: UP032
            listId=destination_list_id, sourceListId=source_list_id
        ),
        method="PUT",
        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=["crm.lists.read"]))
async def get_record_list_memberships(
    context: ToolContext,
    object_type_id: Annotated[
        str, "Specify the object type ID of the record to retrieve its list memberships."
    ],
    record_id: Annotated[
        str, "The unique identifier of the CRM record whose list memberships you want to retrieve."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/records/{objectTypeId}/{recordId}/memberships_getLists'.",  # noqa: E501
]:
    """Retrieve lists a CRM record is a member of.

    This tool retrieves the lists that a specified CRM record is a member of, helping track its association with different lists."""  # 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/crm/v3/lists/records/{objectTypeId}/{recordId}/memberships".format(  # noqa: UP032
            objectTypeId=object_type_id, recordId=record_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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def delete_crm_folder(
    context: ToolContext,
    folder_id_to_delete: Annotated[str, "The ID of the folder to be deleted in HubSpot CRM."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/lists/folders/{folderId}_remove'.",
]:
    """Deletes a specified CRM folder by ID.

    Use this tool to delete a folder in HubSpot CRM by providing the folder ID. It should be called when a specific folder needs to be permanently removed from the CRM 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/crm/v3/lists/folders/{folderId}".format(  # noqa: UP032
            folderId=folder_id_to_delete
        ),
        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=["crm.lists.read"]))
async def retrieve_folders_with_child_nodes(
    context: ToolContext,
    target_folder_id: Annotated[
        str | None,
        "The ID of the folder to retrieve and include all child nodes recursively from HubSpot CRM.",  # noqa: E501
    ] = "0",
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/lists/folders_getAll'."
]:
    """Retrieve folders and include all child folders recursively.

    This tool is used to retrieve a folder from HubSpot CRM and recursively include all child folders via the childNodes attribute, while child lists in these nodes will be empty. Only the retrieved folder will include its child lists."""  # 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/crm/v3/lists/folders",
        method="GET",
        params=remove_none_values({"folderId": target_folder_id}),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def create_folder_hubspot_crm(
    context: ToolContext,
    folder_name: Annotated[str, "The name of the folder to be created in HubSpot CRM."],
    parent_folder_id: Annotated[
        str | None,
        "The ID of the folder where the new folder will be created. Defaults to root folder (ID: 0) if not specified.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/lists/folders_create'."
]:
    """Creates a folder in HubSpot CRM with specified details.

    Use this tool to create a new folder in HubSpot CRM by providing the necessary information for the folder you want to create."""  # noqa: E501
    request_data: Any = {"parentFolderId": parent_folder_id, "name": folder_name}
    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/crm/v3/lists/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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def remove_records_from_list(
    context: ToolContext,
    list_id: Annotated[
        str, "The ILS ID of the MANUAL or SNAPSHOT list from which records will be removed."
    ],
    record_ids_to_remove: Annotated[
        list[str], "List of record IDs to remove from the HubSpot CRM list."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/lists/{listId}/memberships/remove_remove'.",
]:
    """Remove specified records from a HubSpot CRM list.

    Use this tool to remove records from a HubSpot CRM list with `processingType` of `MANUAL` or `SNAPSHOT`. Records not in the list will be ignored."""  # noqa: E501
    request_data: Any = record_ids_to_remove
    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/crm/v3/lists/{listId}/memberships/remove".format(  # noqa: UP032
            listId=list_id
        ),
        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=["crm.lists.read"]))
async def fetch_hubspot_list_memberships(
    context: ToolContext,
    list_identifier: Annotated[str, "The ILS ID of the HubSpot list to retrieve memberships for."],
    before_offset_token: Annotated[
        str | None,
        "The paging offset token for the page before the previously requested records, used to sort records in descending order.",  # noqa: E501
    ] = None,
    number_of_records_to_return: Annotated[
        int | None,
        "Defines how many records to retrieve in the response, with a maximum value of 250.",
    ] = 100,
    paging_offset_after_token: Annotated[
        str | None,
        "The paging offset token for the page that comes after the previously requested records. If provided, records will follow this offset, sorted in ascending order. Takes precedence over the before offset.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/lists/{listId}/memberships_getPage'.",
]:
    """Retrieve memberships of a HubSpot list by order of record ID.

    Use this tool to fetch the memberships of a specific list in HubSpot CRM, sorted by `recordId`. The results can be ordered in ascending or descending order based on the presence of an `after` or `before` offset."""  # 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/crm/v3/lists/{listId}/memberships".format(  # noqa: UP032
            listId=list_identifier
        ),
        method="GET",
        params=remove_none_values({
            "after": paging_offset_after_token,
            "before": before_offset_token,
            "limit": number_of_records_to_return,
        }),
        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=["crm.lists.write", "cms.membership.access_groups.write", "crm.lists.read"],
    )
)
async def remove_all_list_memberships(
    context: ToolContext,
    list_id: Annotated[
        str,
        "The ILS ID of a MANUAL or SNAPSHOT list in HubSpot CRM. Required for removing all memberships.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/lists/{listId}/memberships_removeAll'.",
]:
    """Remove all records from a CRM list without deleting the list.

    Use this tool to remove all memberships from a HubSpot CRM list that has a processing type of MANUAL or SNAPSHOT. Suitable for lists with fewer than 100,000 memberships."""  # 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/crm/v3/lists/{listId}/memberships".format(listId=list_id),  # noqa: UP032
        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=["crm.objects.contacts.read"]))
async def get_meeting_details_by_id(
    context: ToolContext,
    meeting_identifier: Annotated[
        str, "Unique identifier for the meeting you want to retrieve details for."
    ],
    only_archived: Annotated[
        bool | None, "Set to true to return only archived meeting results."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma separated list of properties to return with history of values. Ignored if not present on the object.",  # noqa: E501
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "List of object types to fetch associated IDs for. Nonexistent associations will be ignored.",  # noqa: E501
    ] = None,
    return_properties: Annotated[
        list[str] | None,
        "A list of properties to be returned in the response. If any specified properties are not present, they will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "The property name whose values uniquely identify the meeting object."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/meetings/{meetingId}_getById'.",
]:
    """Retrieve detailed information about a specific meeting.

    Use this tool to get information about a meeting using its unique ID. You can specify properties to control what details are returned."""  # 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/crm/v3/objects/meetings/{meetingId}".format(  # noqa: UP032
            meetingId=meeting_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "archived": only_archived,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def delete_meeting(
    context: ToolContext,
    meeting_id: Annotated[
        str,
        "The unique ID of the meeting to be moved to the recycling bin. This is required to identify the specific meeting.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/meetings/{meetingId}_archive'.",
]:
    """Move a meeting to the recycling bin using its ID.

    This tool is used to move a meeting, identified by its meeting ID, to the recycling bin in the HubSpot CRM. It is useful when you need to archive or remove a meeting from active records."""  # 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/crm/v3/objects/meetings/{meetingId}".format(  # noqa: UP032
            meetingId=meeting_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=["crm.objects.contacts.write"]))
async def update_hubspot_meeting(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    meeting_id: Annotated[
        str | None,
        "The internal ID of the meeting or a property name with unique values for identification.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of the unique property for identifying the meeting.  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-/crm/v3/objects/meetings/{meetingId}_update'.",
]:
    """Update specific properties of a HubSpot meeting.

    Use this tool to perform a partial update on a HubSpot CRM meeting object. Identify the meeting using `{meetingId}` or a unique property with `idProperty`. Overwrite specified property values, but note that read-only or non-existent properties will cause an error. Properties can be cleared by sending an empty string.

    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["UPDATEHUBSPOTMEETING"],
            "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 meeting_id:
        missing_params.append(("meeting_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["UPDATEHUBSPOTMEETING"] + "\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["UPDATEHUBSPOTMEETING"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/{meetingId}".format(  # noqa: UP032
            meetingId=meeting_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTMEETING"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def update_meetings_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-/crm/v3/objects/meetings/batch/update_update'.",
]:
    """Update a batch of meetings in HubSpot CRM.

    This tool updates multiple meetings in HubSpot CRM using internal IDs or unique property values. It should be called when you need to modify details for several meetings 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["UPDATEMEETINGSBATCH"],
            "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["UPDATEMEETINGSBATCH"] + "\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["UPDATEMEETINGSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMEETINGSBATCH"],
        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=["crm.objects.contacts.read"]))
async def retrieve_meeting_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to retrieve only archived meeting records.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/meetings/batch/read_read'.",
]:
    """Retrieve meeting records by ID or unique property.

    Use this tool to retrieve HubSpot CRM meeting records by specifying record IDs or a custom unique value property. It helps in accessing specific meeting details efficiently.

    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["RETRIEVEMEETINGRECORDS"],
            "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["RETRIEVEMEETINGRECORDS"] + "\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["RETRIEVEMEETINGRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEMEETINGRECORDS"],
        params=remove_none_values({"archived": return_only_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=["crm.objects.contacts.write"]))
async def archive_meetings_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-/crm/v3/objects/meetings/batch/archive_archive'.",
]:
    """Archive multiple meetings by IDs in batch.

    Use this tool to archive a batch of meetings by their IDs in HubSpot CRM. It is suitable for managing and cleaning up old or unnecessary meeting data efficiently.

    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["ARCHIVEMEETINGSBATCH"],
            "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["ARCHIVEMEETINGSBATCH"] + "\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["ARCHIVEMEETINGSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEMEETINGSBATCH"],
        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=["crm.objects.contacts.read"]))
async def get_meetings_page(
    context: ToolContext,
    maximum_results_per_page: Annotated[
        int | None,
        "The maximum number of meeting results to display per page. Specify an integer value.",
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token indicating the last successfully read resource, used for paging through results.",  # noqa: E501
    ] = None,
    properties: Annotated[
        list[str] | None,
        "A list of property names to return in the response. If any are not present, they will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to retrieve with their change history. Reduces max meetings returned per request.",  # noqa: E501
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "List object types to retrieve associated IDs for; ignored if associations don't exist.",
    ] = None,
    return_archived_results: Annotated[
        bool | None, "Set to true to return only archived results; false to include active results."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/meetings_getPage'."
]:
    """Retrieve a page of meetings data from HubSpot CRM.

    Use this tool to read a page of meetings from HubSpot CRM. You can specify which properties to return using query parameters."""  # 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/crm/v3/objects/meetings",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "archived": return_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=["crm.objects.contacts.write"]))
async def create_hubspot_meeting(
    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-/crm/v3/objects/meetings_create'."
]:
    """Create a meeting in HubSpot and get its details.

    This tool is used to create a meeting in HubSpot CRM with specific properties. It returns the details of the created meeting, including the unique ID.

    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["CREATEHUBSPOTMEETING"],
            "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["CREATEHUBSPOTMEETING"] + "\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["CREATEHUBSPOTMEETING"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTMEETING"],
        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=["crm.objects.contacts.write"]))
async def upsert_meetings(
    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-/crm/v3/objects/meetings/batch/upsert_upsert'.",
]:
    """Create or update meeting records in HubSpot CRM.

    Use this tool to create new meetings or update existing ones in HubSpot CRM by specifying a unique identifier for each record.

    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["UPSERTMEETINGS"],
            "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["UPSERTMEETINGS"] + "\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["UPSERTMEETINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTMEETINGS"],
        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=["crm.objects.contacts.write"]))
async def create_batch_meetings(
    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-/crm/v3/objects/meetings/batch/create_create'.",
]:
    """Create a batch of meetings in HubSpot CRM.

    Use this tool to efficiently create multiple meetings at once in HubSpot CRM. Ideal for scheduling large numbers of meetings in a single operation.

    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["CREATEBATCHMEETINGS"],
            "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["CREATEBATCHMEETINGS"] + "\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["CREATEBATCHMEETINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHMEETINGS"],
        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=["crm.objects.contacts.read"]))
async def search_hubspot_meetings(
    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-/crm/v3/objects/meetings/search_doSearch'.",
]:
    """Search for meetings in HubSpot CRM.

    Use this tool to search for meetings stored in the HubSpot CRM. It should be called whenever there is a need to locate meetings based on specific criteria within 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["SEARCHHUBSPOTMEETINGS"],
            "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["SEARCHHUBSPOTMEETINGS"] + "\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["SEARCHHUBSPOTMEETINGS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/meetings/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTMEETINGS"],
        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=["crm.objects.contacts.read"]))
async def search_hubspot_notes(
    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-/crm/v3/objects/notes/search_doSearch'."
]:
    """Search for notes in HubSpot CRM.

    Use this tool to search for specific notes within HubSpot CRM. It should be called when you need to find and retrieve notes based on particular search criteria.

    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["SEARCHHUBSPOTNOTES"],
            "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["SEARCHHUBSPOTNOTES"] + "\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["SEARCHHUBSPOTNOTES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTNOTES"],
        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=["crm.objects.contacts.read"]))
async def retrieve_notes_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "Token for paging to retrieve the next set of notes. Use the token from `paging.next.after` in the previous response.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their historical values. Reduces the maximum number of notes per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None,
        "The maximum number of note results to display per page. Specify an integer value.",
    ] = 10,
    return_only_archived_notes: Annotated[
        bool | None,
        "Set to True to return only archived notes; otherwise, non-archived notes are returned.",
    ] = False,
    returned_properties_list: Annotated[
        list[str] | None,
        "List of note properties to include in the response. Specify as an array of strings.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/notes_getPage'."
]:
    """Retrieve a page of notes from HubSpot CRM.

    Use this tool to read a page of notes from HubSpot CRM. You can control the properties returned via query parameters."""  # 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/crm/v3/objects/notes",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": returned_properties_list,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived_notes,
        }),
        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=["crm.objects.contacts.write"]))
async def create_note_in_hubspot(
    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-/crm/v3/objects/notes_create'."
]:
    """Create a note in HubSpot CRM and return its details.

    Use this tool to create a note in HubSpot CRM with specified properties. It returns a copy of the created note object, including its ID.

    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["CREATENOTEINHUBSPOT"],
            "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["CREATENOTEINHUBSPOT"] + "\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["CREATENOTEINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATENOTEINHUBSPOT"],
        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=["crm.objects.contacts.read"]))
async def retrieve_notes_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived records. Use false to include non-archived records.  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-/crm/v3/objects/notes/batch/read_read'."
]:
    """Retrieve notes records by ID or custom property.

    This tool retrieves records from HubSpot CRM using either record IDs or a custom unique value property specified by the `idProperty` parameter.

    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["RETRIEVENOTESRECORDS"],
            "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["RETRIEVENOTESRECORDS"] + "\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["RETRIEVENOTESRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVENOTESRECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.contacts.write"]))
async def upsert_notes_hubspot(
    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-/crm/v3/objects/notes/batch/upsert_upsert'.",
]:
    """Create or update notes in HubSpot CRM by unique property.

    This tool is used to create or update multiple notes in HubSpot CRM in a single request. It identifies records using a unique property value specified by the `idProperty` query parameter.

    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["UPSERTNOTESHUBSPOT"],
            "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["UPSERTNOTESHUBSPOT"] + "\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["UPSERTNOTESHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTNOTESHUBSPOT"],
        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=["crm.objects.contacts.write"]))
async def archive_notes_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-/crm/v3/objects/notes/batch/archive_archive'.",
]:
    """Archive a batch of notes by their IDs.

    Use this tool to archive multiple notes in HubSpot CRM by providing their IDs. Ideal for managing large numbers of notes that need to be archived simultaneously.

    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["ARCHIVENOTESBATCH"],
            "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["ARCHIVENOTESBATCH"] + "\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["ARCHIVENOTESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVENOTESBATCH"],
        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=["crm.objects.contacts.write"]))
async def batch_update_notes(
    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-/crm/v3/objects/notes/batch/update_update'.",
]:
    """Update multiple notes in HubSpot CRM by ID or property.

    Use this tool to update a batch of notes in HubSpot CRM by their internal IDs or unique property values. Ideal for situations where multiple notes need simultaneous modifications.

    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["BATCHUPDATENOTES"],
            "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["BATCHUPDATENOTES"] + "\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["BATCHUPDATENOTES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHUPDATENOTES"],
        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=["crm.objects.contacts.write"]))
async def create_batch_of_notes(
    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-/crm/v3/objects/notes/batch/create_create'.",
]:
    """Create multiple notes in a CRM batch operation.

    Use this tool to create a batch of notes in HubSpot CRM. Ideal for adding multiple notes efficiently in a single operation.

    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["CREATEBATCHOFNOTES"],
            "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["CREATEBATCHOFNOTES"] + "\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["CREATEBATCHOFNOTES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFNOTES"],
        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=["crm.objects.contacts.read"]))
async def get_note_details(
    context: ToolContext,
    note_id: Annotated[str, "The unique identifier of the note to retrieve details for."],
    associated_object_types: Annotated[
        list[str] | None,
        "List the object types to retrieve associated IDs for, separated by commas. Invalid types are ignored.",  # noqa: E501
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "A list of property names to return for the note. Non-existing properties will be ignored.",
    ] = None,
    return_archived_results_only: Annotated[
        bool | None, "Set to true to return only archived notes."
    ] = False,
    return_properties_with_history: Annotated[
        list[str] | None,
        "List properties to return with their history of previous values. Ignored if not present on the object.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "Specify the unique property name to identify the object in HubSpot CRM."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/notes/{noteId}_getById'."
]:
    """Retrieve details of a note by its unique ID.

    Use this tool to get details of a note from HubSpot CRM using its unique ID. You can specify additional properties to control the returned 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/crm/v3/objects/notes/{noteId}".format(noteId=note_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": return_properties_with_history,
            "associations": associated_object_types,
            "archived": return_archived_results_only,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def delete_note_hubspot(
    context: ToolContext,
    note_id: Annotated[str, "The unique identifier of the note to be archived in HubSpot CRM."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/notes/{noteId}_archive'.",
]:
    """Move a HubSpot note to the recycling bin.

    Use this tool to move a note identified by `noteId` in HubSpot CRM to the recycling bin. This is useful for soft-deleting notes when they are no longer needed in the immediate CRM workspace."""  # 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/crm/v3/objects/notes/{noteId}".format(noteId=note_id),  # noqa: UP032
        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=["crm.objects.contacts.write"]))
async def update_hubspot_note(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    note_identifier: Annotated[
        str | None,
        "The ID or unique property value of the note to update. Use `noteId` for internal ID or specify a unique property via `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property with unique values for this object, used to identify the note.  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-/crm/v3/objects/notes/{noteId}_update'."
]:
    """Update a HubSpot note with new property values.

    Use this tool to update specific properties of a HubSpot CRM note. Identify the note by its ID or a unique property value and provide new values for properties you wish to change. Read-only and non-existent properties cannot be updated. Clear properties by passing an empty string.

    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["UPDATEHUBSPOTNOTE"],
            "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 note_identifier:
        missing_params.append(("note_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["UPDATEHUBSPOTNOTE"] + "\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["UPDATEHUBSPOTNOTE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/notes/{noteId}".format(noteId=note_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTNOTE"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def fetch_enablement_data(
    context: ToolContext,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/object-library/enablement'."
]:
    """Fetch enablement data from HubSpot CRM.

    Use this tool to obtain enablement information from the HubSpot CRM. It should be called when detailed enablement data is required for CRM-related operations or analyses."""  # 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/crm/v3/object-library/enablement",
        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=[
            "crm.objects.carts.read",
            "crm.objects.services.write",
            "tickets.sensitive.v2",
            "crm.objects.contacts.write",
            "crm.objects.courses.read",
            "crm.objects.goals.write",
            "crm.objects.custom.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.commercepayments.read",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.leads.read",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.partner-services.write",
            "e-commerce",
            "crm.objects.products.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.objects.orders.write",
            "crm.objects.subscriptions.write",
            "tickets.highly_sensitive.v2",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.objects.companies.write",
            "crm.objects.partner-services.read",
            "tickets",
            "crm.objects.quotes.read",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.companies.read",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.carts.write",
            "crm.objects.quotes.write",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.appointments.write",
            "crm.objects.partner-clients.write",
            "crm.objects.line_items.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.appointments.read",
            "crm.objects.invoices.read",
            "crm.objects.partner-clients.read",
            "crm.objects.courses.write",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.listings.write",
            "crm.objects.subscriptions.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.commercepayments.write",
            "crm.objects.leads.write",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.goals.read",
            "crm.objects.orders.read",
            "crm.objects.contacts.sensitive.read.v2",
            "media_bridge.read",
            "crm.objects.deals.sensitive.write.v2",
            "crm.objects.products.write",
        ],
    )
)
async def enable_object_type_in_hubspot(
    context: ToolContext,
    object_type_id: Annotated[
        str, "The unique identifier for the object type in HubSpot CRM that needs to be enabled."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/object-library/enablement/{objectTypeId}'.",
]:
    """Enable an object type in HubSpot CRM via its ID.

    This tool is used to enable a specific object type in HubSpot's CRM system using the object's unique ID. It should be called when there is a need to activate or verify the activation status of a specific object category in HubSpot."""  # 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/crm/v3/object-library/enablement/{objectTypeId}".format(  # noqa: UP032
            objectTypeId=object_type_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=["crm.objects.orders.read"]))
async def search_order_records(
    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-/crm/v3/objects/orders/search'."
]:
    """Search for order records in HubSpot CRM.

    This tool searches order records in HubSpot CRM based on specified criteria. It should be called when you need to find specific order details or filter orders according to certain conditions.

    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["SEARCHORDERRECORDS"],
            "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["SEARCHORDERRECORDS"] + "\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["SEARCHORDERRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHORDERRECORDS"],
        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=["crm.objects.orders.read"]))
async def retrieve_hubspot_orders(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived_orders: Annotated[
        bool | None,
        "Set to True to return only archived order records from HubSpot CRM.  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-/crm/v3/objects/orders/batch/read'."
]:
    """Retrieve order records from HubSpot CRM by ID or custom property.

    Use this tool to fetch order records from HubSpot CRM by providing record IDs or a custom unique value property. Ideal for accessing specific order details stored in your HubSpot account.

    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["RETRIEVEHUBSPOTORDERS"],
            "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["RETRIEVEHUBSPOTORDERS"] + "\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["RETRIEVEHUBSPOTORDERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTORDERS"],
        params=remove_none_values({"archived": return_only_archived_orders}),
        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=["crm.objects.orders.read"]))
async def get_order_details(
    context: ToolContext,
    order_identifier: Annotated[
        str,
        "The unique identifier for the order. This can be the internal object ID or a unique property value specified by the idProperty.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "Comma separated list of object types to retrieve associated IDs. Non-existent associations are ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history of previous values. If specified properties are not present, they will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results for the specified order."
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List the properties to retrieve for the order. Any nonexistent properties will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of a unique property to identify the order. Overrides the default ID.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/orders/{orderId}'."
]:
    """Retrieve details of an order using its ID.

    Call this tool to get detailed information about a specific order from the HubSpot CRM by using the order's ID. You can control which properties of the order are returned by specifying them in the `properties` 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/crm/v3/objects/orders/{orderId}".format(  # noqa: UP032
            orderId=order_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.orders.write"]))
async def delete_order_by_id(
    context: ToolContext,
    order_id: Annotated[
        str, "The unique ID of the order to delete, moving it to the recycling bin."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/orders/{orderId}'."
]:
    """Deletes an order by its ID from the CRM.

    Use this tool to delete an order from the CRM system by providing its unique ID. The order will be moved to the recycling bin, allowing for potential recovery later 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/crm/v3/objects/orders/{orderId}".format(orderId=order_id),  # noqa: UP032
        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=["crm.objects.orders.write"]))
async def update_order_details(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    order_id: Annotated[
        str | None,
        "The internal ID of the order to be updated.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of a unique property to identify the order object instead of using the order ID.  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-/crm/v3/objects/orders/{orderId}'."
]:
    """Update specific details of an order using its ID.

    This tool performs a partial update of an order in HubSpot CRM, identified by the order's internal ID or a unique property value. Use it to modify existing order properties; read-only or non-existent properties cannot be updated and will cause errors. Properties can be cleared by providing empty values.

    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["UPDATEORDERDETAILS"],
            "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 order_id:
        missing_params.append(("order_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["UPDATEORDERDETAILS"] + "\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["UPDATEORDERDETAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/{orderId}".format(orderId=order_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEORDERDETAILS"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.orders.write"]))
async def upsert_orders_in_hubspot(
    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-/crm/v3/objects/orders/batch/upsert'."
]:
    """Create or update orders in HubSpot CRM.

    This tool calls the HubSpot CRM API to create or update multiple order records based on a unique property value. Use it when you need to batch insert or update orders by specifying a unique identifier for each order.

    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["UPSERTORDERSINHUBSPOT"],
            "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["UPSERTORDERSINHUBSPOT"] + "\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["UPSERTORDERSINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTORDERSINHUBSPOT"],
        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=["crm.objects.orders.write"]))
async def create_batch_orders(
    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-/crm/v3/objects/orders/batch/create'."
]:
    """Create a batch of orders in HubSpot CRM.

    Use this tool to create multiple orders at once in HubSpot CRM. It is suitable when you need to efficiently process and record several orders simultaneously.

    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["CREATEBATCHORDERS"],
            "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["CREATEBATCHORDERS"] + "\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["CREATEBATCHORDERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHORDERS"],
        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=["crm.objects.orders.write"]))
async def update_hubspot_orders_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-/crm/v3/objects/orders/batch/update'."
]:
    """Update multiple HubSpot CRM orders in a batch.

    Use this tool to update a batch of orders in HubSpot CRM by their internal ID or unique property values. Useful for modifying multiple order records 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["UPDATEHUBSPOTORDERSBATCH"],
            "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["UPDATEHUBSPOTORDERSBATCH"] + "\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["UPDATEHUBSPOTORDERSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTORDERSBATCH"],
        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=["crm.objects.orders.read"]))
async def get_orders_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for; ignored if not existing.",
    ] = None,
    only_return_archived_orders: Annotated[
        bool | None,
        "Set to true to return only archived orders. False returns both archived and unarchived orders.",  # noqa: E501
    ] = False,
    order_properties_to_return: Annotated[
        list[str] | None,
        "A list of properties to be included in the order response. Specify as a comma-separated string. Ignored if not present on the objects.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None,
        "Token for pagination, representing the last successfully read resource for fetching the next page of results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of order properties to return with their history of previous values. Reduces maximum results per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None,
        "The maximum number of orders to display per page. This controls the pagination size.",
    ] = 10,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/orders'."]:
    """Retrieve a page of orders from CRM.

    This tool fetches a page of orders from the CRM. You can specify which properties to include in the results using the query parameters. Use this tool to list orders when 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/crm/v3/objects/orders",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": order_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived_orders,
        }),
        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=["crm.objects.orders.write"]))
async def create_hubspot_order(
    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-/crm/v3/objects/orders'."]:
    """Create a new order in HubSpot CRM with specified properties.

    Use this tool to create a new order in the HubSpot CRM system. It returns the details and ID of the created order, allowing further tracking and 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["CREATEHUBSPOTORDER"],
            "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["CREATEHUBSPOTORDER"] + "\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["CREATEHUBSPOTORDER"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTORDER"],
        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=["crm.objects.orders.write"]))
async def archive_orders_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-/crm/v3/objects/orders/batch/archive'."
]:
    """Archive a batch of orders by ID in HubSpot CRM.



    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.
    """
    if mode == ToolMode.GET_REQUEST_SCHEMA:
        return {
            "request_body_schema": REQUEST_BODY_SCHEMAS["ARCHIVEORDERSBATCH"],
            "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["ARCHIVEORDERSBATCH"] + "\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["ARCHIVEORDERSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/orders/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEORDERSBATCH"],
        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=["crm.objects.partner-clients.write"]))
async def update_partner_clients_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-/crm/v3/objects/partner_clients/batch/update'.",
]:
    """Update multiple partner clients in a batch.

    Use this tool to update information for multiple partner clients simultaneously in HubSpot CRM. It should be called when you need to make bulk updates to partner client records.

    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["UPDATEPARTNERCLIENTSBATCH"],
            "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["UPDATEPARTNERCLIENTSBATCH"] + "\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["UPDATEPARTNERCLIENTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_clients/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPARTNERCLIENTSBATCH"],
        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=["crm.objects.partner-clients.read"]))
async def get_partner_clients(
    context: ToolContext,
    client_properties: Annotated[
        list[str] | None,
        "A list of specific client properties to retrieve, such as 'name', 'email', etc.",
    ] = None,
    include_archived: Annotated[
        bool | None, "Set to true to include archived partner clients, false to exclude them."
    ] = False,
    include_associations: Annotated[
        list[str] | None,
        "List of associations to include in the response. Specify names of associations as strings.",  # noqa: E501
    ] = None,
    pagination_cursor: Annotated[
        str | None,
        "A string token to retrieve the next page of partner clients, obtained from a previous response.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List property names to retrieve along with their historical versions. Each property should be specified as a string.",  # noqa: E501
    ] = None,
    retrieval_limit: Annotated[
        int | None,
        "Specifies the maximum number of partner clients to retrieve from the HubSpot CRM in a single call.",  # noqa: E501
    ] = 10,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/partner_clients'."
]:
    """Retrieve partner clients from HubSpot CRM.

    This tool retrieves a list of partner clients from the HubSpot CRM. It should be called when users need to access details about their partner clients stored in HubSpot's CRM."""  # 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/crm/v3/objects/partner_clients",
        method="GET",
        params=remove_none_values({
            "limit": retrieval_limit,
            "after": pagination_cursor,
            "properties": client_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": include_associations,
            "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=["crm.objects.partner-clients.read"]))
async def batch_read_partner_clients(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    include_archived: Annotated[
        bool | None,
        "Set to true to include archived partner clients in the batch results. Default is 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-/crm/v3/objects/partner_clients/batch/read'.",
]:
    """Fetch batch details of partner clients in HubSpot CRM.

    Use this tool to retrieve information for multiple partner clients at once from HubSpot CRM. Ideal for scenarios where bulk data about partner clients is needed.

    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["BATCHREADPARTNERCLIENTS"],
            "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["BATCHREADPARTNERCLIENTS"] + "\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["BATCHREADPARTNERCLIENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_clients/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHREADPARTNERCLIENTS"],
        params=remove_none_values({"archived": include_archived}),
        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=["crm.objects.partner-clients.read"]))
async def search_partner_clients(
    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-/crm/v3/objects/partner_clients/search'."
]:
    """Perform a search for partner clients in CRM.

    Use this tool to search for partner clients within the HubSpot CRM system. It should be called when you need to find specific partner client information or records based on search criteria.

    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["SEARCHPARTNERCLIENTS"],
            "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["SEARCHPARTNERCLIENTS"] + "\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["SEARCHPARTNERCLIENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_clients/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHPARTNERCLIENTS"],
        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=["crm.objects.partner-clients.read"]))
async def get_partner_client_info(
    context: ToolContext,
    partner_client_id: Annotated[
        str, "The unique identifier for the partner client to retrieve details from HubSpot CRM."
    ],
    associated_objects: Annotated[
        list[str] | None,
        "A list of associated object types to include, such as 'contacts' or 'deals'.",
    ] = None,
    include_archived_data: Annotated[
        bool | None,
        "Boolean to specify if archived partner client data should be included in the response. Set to true to include archived data.",  # noqa: E501
    ] = False,
    optional_properties: Annotated[
        list[str] | None,
        "An array of specific properties to retrieve for the partner client. Leave empty to obtain all available properties.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify the list of properties to retrieve, including their historical values.",
    ] = None,
    property_id: Annotated[
        str | None,
        "Specify which property should be used as the primary identifier for the partner client. Useful for custom identification schemes.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/partner_clients/{partnerClientId}'.",
]:
    """Retrieve information for a specific partner client.

    Call this tool to obtain details about a specific partner client using their unique identifier within the HubSpot CRM."""  # 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/crm/v3/objects/partner_clients/{partnerClientId}".format(  # noqa: UP032
            partnerClientId=partner_client_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": optional_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_objects,
            "archived": include_archived_data,
            "idProperty": property_id,
        }),
        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=["crm.objects.partner-clients.write"]))
async def update_partner_client(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    partner_client_id: Annotated[
        str | None,
        "The unique identifier for the partner client to be updated in HubSpot CRM.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    identifier_property: Annotated[
        str | None,
        "Specify the property name used to identify the partner client for update operations.  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-/crm/v3/objects/partner_clients/{partnerClientId}'.",
]:
    """Update details of a partner client in HubSpot CRM.

    This tool is used to modify the information of a specified partner client in HubSpot CRM. It should be called when there is a need to update or change details related to a partner client using their unique 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["UPDATEPARTNERCLIENT"],
            "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 partner_client_id:
        missing_params.append(("partner_client_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["UPDATEPARTNERCLIENT"] + "\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["UPDATEPARTNERCLIENT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_clients/{partnerClientId}".format(  # noqa: UP032
            partnerClientId=partner_client_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPARTNERCLIENT"],
        params=remove_none_values({"idProperty": identifier_property}),
        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=["crm.objects.partner-clients.write"]))
async def associate_partner_client_with_object(
    context: ToolContext,
    association_type: Annotated[
        str,
        "Specifies the type of association (e.g., contact, company, deal) between the partner client and the object.",  # noqa: E501
    ],
    partner_client_id: Annotated[
        str,
        "The unique identifier for the partner client you wish to associate with another object. This should be a string representing the partner client's ID in the CRM system.",  # noqa: E501
    ],
    target_object_id: Annotated[
        str,
        "The unique identifier of the CRM object you are associating with the partner client. This could be any valid object ID such as that of a contact, company, or deal.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of the object to associate with the partner client (e.g., contact, company, deal).",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Associate a partner client with another CRM object.

    Use this tool to create an association between a partner client and another object in the CRM system, such as a contact, company, or deal. This can help in linking related entities for better data management and relationship tracking."""  # 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/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            partnerClientId=partner_client_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        method="PUT",
        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=["crm.objects.partner-clients.write"]))
async def remove_partner_client_association(
    context: ToolContext,
    association_type: Annotated[
        str,
        "The type of association to be removed between the partner client and the object. This defines the nature of their relationship.",  # noqa: E501
    ],
    partner_client_id: Annotated[
        str,
        "The unique identifier for the partner client to be disassociated. This should be a string that identifies the specific partner client in the HubSpot CRM.",  # noqa: E501
    ],
    target_object_id: Annotated[
        str, "The unique identifier of the object associated with the target partner client."
    ],
    target_object_type: Annotated[
        str,
        "Specify the type of the object you are dissociating from the partner client, such as 'contacts' or 'companies'.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Remove an association between two partner clients in HubSpot CRM.

    This tool removes an existing association between two partner clients in the HubSpot CRM system. It should be called when you need to disassociate a partner client from another object, identified by specific IDs and association type."""  # 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/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            partnerClientId=partner_client_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        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=["crm.objects.partner-clients.read"]))
async def list_partner_client_associations(
    context: ToolContext,
    partner_client_id: Annotated[
        str,
        "The unique identifier for the partner client whose associations are to be retrieved. It is required and must be a valid string.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of object to which the partner client is associated. Specify using a string value representing the object type, such as 'contact' or 'deal'.",  # noqa: E501
    ],
    include_family_associations: Annotated[
        bool | None,
        "Indicate whether to include family associations in the response. Set to true to include them.",  # noqa: E501
    ] = False,
    max_results_per_page: Annotated[
        int | None,
        "Specify the maximum number of results to display per page. Use an integer value.",
    ] = 500,
    paging_cursor_token: Annotated[
        str | None, "The token used for pagination to fetch the next set of results."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}'.",  # noqa: E501
]:
    """Retrieve associations of a partner client by type.

    Use this tool to list associations for a specific partner client in HubSpot CRM. It helps in retrieving related objects based on the specified type."""  # 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/crm/v3/objects/partner_clients/{partnerClientId}/associations/{toObjectType}".format(  # noqa: UP032
            partnerClientId=partner_client_id, toObjectType=target_object_type
        ),
        method="GET",
        params=remove_none_values({
            "after": paging_cursor_token,
            "limit": max_results_per_page,
            "includeFA": include_family_associations,
        }),
        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=["crm.objects.partner-services.read"]))
async def get_partner_service_details(
    context: ToolContext,
    partner_service_id: Annotated[
        str,
        "The unique ID of the partner service object to retrieve. This can be the internal object ID or a unique property value as specified by the `idProperty`.",  # noqa: E501
    ],
    properties_to_return: Annotated[
        list[str] | None,
        "A list of property names to return for the partner service. Any missing properties will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties whose historical values should be returned for the partner service object.",  # noqa: E501
    ] = None,
    retrieve_associated_ids: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs. Non-existent associations are ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results, false otherwise."
    ] = False,
    unique_property_name: Annotated[
        str | None, "The name of a unique property to identify the object."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/partner_services/{partnerServiceId}'.",
]:
    """Retrieve details of a partner service by ID.

    Use this tool to get information about a specific partner service object using its ID. You can specify which properties to return or use the default settings to retrieve comprehensive details."""  # 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/crm/v3/objects/partner_services/{partnerServiceId}".format(  # noqa: UP032
            partnerServiceId=partner_service_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_ids,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.partner-services.write"]))
async def update_partner_service(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    partner_service_id: Annotated[
        str | None,
        "The internal object ID of the partner service to update. Use this to specify the object you want to partially update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of a unique property for the partner service object to identify it. This is used instead of the default internal ID.  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-/crm/v3/objects/partner_services/{partnerServiceId}'.",
]:
    """Partially update a partner service object in HubSpot CRM.

    Use this tool to update specific properties of a partner service object identified by `partnerServiceId` in HubSpot CRM. You can also use a unique property value specified by `idProperty`. Only existing, non-read-only properties can be updated. To clear a property, pass an empty string.

    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["UPDATEPARTNERSERVICE"],
            "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 partner_service_id:
        missing_params.append(("partner_service_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["UPDATEPARTNERSERVICE"] + "\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["UPDATEPARTNERSERVICE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_services/{partnerServiceId}".format(  # noqa: UP032
            partnerServiceId=partner_service_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPARTNERSERVICE"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.partner-services.read"]))
async def retrieve_partner_services_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived_records: Annotated[
        bool | None,
        "Set to true to return only the archived records. False will include non-archived records.  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-/crm/v3/objects/partner_services/batch/read'.",
]:
    """Retrieve partner services records by ID or unique property.

    This tool retrieves records for partner services using either a record ID or a custom unique value property. It is ideal for accessing specific partner service data within the HubSpot CRM.

    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["RETRIEVEPARTNERSERVICESRECORDS"],
            "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["RETRIEVEPARTNERSERVICESRECORDS"]
                + "\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["RETRIEVEPARTNERSERVICESRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_services/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEPARTNERSERVICESRECORDS"],
        params=remove_none_values({"archived": return_only_archived_records}),
        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=["crm.objects.partner-services.read"]))
async def search_partner_services_hubspot(
    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-/crm/v3/objects/partner_services/search'."
]:
    """Search for partner services in HubSpot CRM.

    Use this tool to perform a search for partner services within the HubSpot CRM. It can be called when you need to find specific partner services based on given criteria or parameters.

    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["SEARCHPARTNERSERVICESHUBSPOT"],
            "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["SEARCHPARTNERSERVICESHUBSPOT"]
                + "\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["SEARCHPARTNERSERVICESHUBSPOT"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_services/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHPARTNERSERVICESHUBSPOT"],
        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=["crm.objects.partner-services.read"]))
async def get_partner_services(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "The maximum number of results to display per page. Must be an integer value."
    ] = 10,
    paging_cursor_after: Annotated[
        str | None, "The cursor token to retrieve the next page of results in a paged response."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of property names to fetch with their history. Reduces max number of results per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "List of properties to return in the response. Ignored if not present on requested objects.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/partner_services'."
]:
    """Retrieve a page of partner services.

    Fetch a list of partner services from the HubSpot CRM. The returned data can be customized using the `properties` 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/crm/v3/objects/partner_services",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_after,
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.partner-services.write"]))
async def associate_partner_service(
    context: ToolContext,
    association_type: Annotated[
        str,
        'Specifies the type of association to create between the partner service and another object (e.g., "owner", "affiliate").',  # noqa: E501
    ],
    partner_service_id: Annotated[
        str,
        "The identifier for the partner service to associate with another object. This should be a valid string representing the unique ID of the partner service in the CRM.",  # noqa: E501
    ],
    target_object_id: Annotated[
        str,
        "The ID of the target object you want to associate with the partner service. This should be a valid object ID in HubSpot CRM.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of CRM object to associate with the partner service, e.g., 'contacts', 'companies', or 'deals'.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'put-/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Associate a partner service with another CRM object.

    Use this tool to associate a partner service with another CRM object in HubSpot, such as contacts, companies, or deals. This is useful when you want to establish connections between different objects in your CRM."""  # 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/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            partnerServiceId=partner_service_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        method="PUT",
        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=["crm.objects.partner-services.write"]))
async def remove_partner_service_association(
    context: ToolContext,
    association_type: Annotated[
        str,
        "Specifies the type of association to remove between the partner services. This is a string value that defines how the services are linked.",  # noqa: E501
    ],
    partner_service_id: Annotated[
        str,
        "The unique identifier for the partner service. It specifies which partner service's association is to be removed.",  # noqa: E501
    ],
    target_object_id: Annotated[
        str,
        "The unique identifier of the object to be disassociated from the partner service. This is required to specify which object is being unlinked.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str,
        "The type of the object to which the partner service is associated. Specify the object category, such as 'contact', 'company', etc.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}/{toObjectId}/{associationType}'.",  # noqa: E501
]:
    """Remove an association between two partner services.

    This tool removes the specified association between partner services in HubSpot CRM. Use it when you need to disassociate two services linked by a specific relationship."""  # 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/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}/{toObjectId}/{associationType}".format(  # noqa: UP032
            partnerServiceId=partner_service_id,
            toObjectType=target_object_type,
            toObjectId=target_object_id,
            associationType=association_type,
        ),
        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=["crm.objects.partner-services.write"]))
async def update_partner_services_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-/crm/v3/objects/partner_services/batch/update'.",
]:
    """Update multiple partner services in CRM by ID or unique properties.

    Use this tool to update a batch of partner services in the HubSpot CRM using internal IDs or unique property values.

    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["UPDATEPARTNERSERVICESBATCH"],
            "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["UPDATEPARTNERSERVICESBATCH"]
                + "\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["UPDATEPARTNERSERVICESBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/partner_services/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPARTNERSERVICESBATCH"],
        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=["crm.objects.partner-services.read"]))
async def list_partner_service_associations(
    context: ToolContext,
    partner_service_id: Annotated[
        str,
        "The unique identifier of the partner service to retrieve associations for. This ID is required.",  # noqa: E501
    ],
    target_object_type: Annotated[
        str, "The type of object to filter associations by, such as contacts, companies, etc."
    ],
    include_associated_fields: Annotated[
        bool | None, "Set to true to include associated fields in the response."
    ] = False,
    max_results_per_page: Annotated[
        int | None,
        "Specify the maximum number of results to display per page. This determines the page size for the response.",  # noqa: E501
    ] = 500,
    paging_cursor_token: Annotated[
        str | None, "The token to continue paginated results from the last read resource."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}'.",  # noqa: E501
]:
    """Retrieve associations of a partner service by type.

    This tool retrieves a list of associations for a specified partner service in HubSpot CRM based on the provided type. It should be called when you need to explore relationships connected to a partner service, filtering by specific object types."""  # 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/crm/v3/objects/partner_services/{partnerServiceId}/associations/{toObjectType}".format(  # noqa: UP032
            partnerServiceId=partner_service_id, toObjectType=target_object_type
        ),
        method="GET",
        params=remove_none_values({
            "after": paging_cursor_token,
            "limit": max_results_per_page,
            "includeFA": include_associated_fields,
        }),
        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"))
async def read_batch_payments(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_return_archived_results: Annotated[
        bool | None,
        "Return only archived results if set to true.  Only used when mode is 'execute'.",
    ] = 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-/crm/v3/objects/payments/batch/read'."
]:
    """Retrieve a batch of payments from CRM by IDs or unique properties.

    Use this tool to get details of multiple payments from the CRM system based on internal IDs or unique property values.

    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["READBATCHPAYMENTS"],
            "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["READBATCHPAYMENTS"] + "\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["READBATCHPAYMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/payments/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["READBATCHPAYMENTS"],
        params=remove_none_values({"archived": only_return_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"))
async def get_payment_details(
    context: ToolContext,
    payment_identifier: Annotated[
        str,
        "The unique identifier for the payment object. This can be the internal object ID or a unique property value.",  # noqa: E501
    ],
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their historical values. Unavailable properties will be ignored.",  # noqa: E501
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "A list of properties to return in the response. Non-existent properties will be ignored.",
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for this payment. Non-existent associations are ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived results in the response."
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "The name of a property uniquely identifying the payment object type. Used to specify a unique property other than the internal ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/payments/{paymentsId}'."
]:
    """Retrieve details of a payment object by ID.

    Use this tool to obtain information about a specific payment object in the CRM by providing its ID. The ID can be the internal object ID or another unique property value. You can control the properties returned using the `properties` 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/crm/v3/objects/payments/{paymentsId}".format(  # noqa: UP032
            paymentsId=payment_identifier
        ),
        method="GET",
        params=remove_none_values({
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "archived": return_only_archived_results,
            "idProperty": unique_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"))
async def get_payment_records(
    context: ToolContext,
    archived_results_only: Annotated[
        bool | None,
        "Indicate if only archived payment records should be returned. Set to true to filter for archived records only.",  # noqa: E501
    ] = False,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If specified associations do not exist, they will be ignored.",  # noqa: E501
    ] = None,
    paging_token_after: Annotated[
        str | None,
        "The cursor token from the last read resource to fetch the next page of results.",
    ] = None,
    payment_properties_to_include: Annotated[
        list[str] | None,
        "A list of property names to include in the response. Specified properties not present on the requested objects will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names to return with their historical values. Ignored if not present on objects.",  # noqa: E501
    ] = None,
    results_per_page_limit: Annotated[
        int | None, "Maximum number of payment records to display per page."
    ] = 10,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/payments'."]:
    """Retrieve a page of payment records from HubSpot CRM.

    Use this tool to get a list of payment records from the HubSpot CRM. You can specify which properties to include in the results by using query parameters."""  # 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/crm/v3/objects/payments",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page_limit,
            "after": paging_token_after,
            "properties": payment_properties_to_include,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": 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"))
async def search_hubspot_payments(
    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-/crm/v3/objects/payments/search'."
]:
    """Search for payments in HubSpot CRM.

    Use this tool to search for specific payments within the HubSpot CRM. It should be called when you need to locate payments based on specific criteria.

    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["SEARCHHUBSPOTPAYMENTS"],
            "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["SEARCHHUBSPOTPAYMENTS"] + "\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["SEARCHHUBSPOTPAYMENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/payments/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTPAYMENTS"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_pipeline_stage_audit(
    context: ToolContext,
    object_type: Annotated[
        str,
        "The CRM object type to query for the pipeline stage, such as 'deals', 'tickets', or 'contacts'.",  # noqa: E501
    ],
    pipeline_id: Annotated[
        str, "The unique identifier for the pipeline to retrieve stage audit logs from."
    ],
    stage_identifier: Annotated[
        str,
        "The unique identifier of the pipeline stage to audit. Use this to specify which stage's mutations you want to retrieve.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}/audit_getAudit'.",  # noqa: E501
]:
    """Retrieve audit logs for a specific pipeline stage.

    This tool retrieves a reverse chronological list of all mutations that have occurred on a specified pipeline stage within HubSpot CRM. It should be called when you need to audit changes or track the history of a pipeline stage."""  # 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/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}/audit".format(  # noqa: UP032
            objectType=object_type, stageId=stage_identifier, pipelineId=pipeline_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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_pipeline_by_id(
    context: ToolContext,
    object_type_in_crm: Annotated[
        str,
        "Specify the type of CRM object, such as 'deals' or 'tickets', whose pipeline you want to retrieve.",  # noqa: E501
    ],
    pipeline_unique_id: Annotated[
        str,
        "The unique identifier for the CRM pipeline to retrieve details. This value is required to fetch the specific pipeline information.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}/{pipelineId}_getById'.",
]:
    """Retrieve a single CRM pipeline by its unique ID.

    Use this tool to get detailed information about a specific pipeline in the HubSpot CRM by providing its unique identifier and object type."""  # 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/crm/v3/pipelines/{objectType}/{pipelineId}".format(  # noqa: UP032
            objectType=object_type_in_crm, pipelineId=pipeline_unique_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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def replace_pipeline_hubspot(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "Specify the object type for the pipeline, such as 'deals' or 'tickets'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    pipeline_identifier: Annotated[
        str | None,
        "The unique identifier of the pipeline to replace in HubSpot CRM. This is required to specify which pipeline is being modified.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    validate_references_before_deletion: Annotated[
        bool | None,
        "Set to true to validate all references before deleting the pipeline.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    validate_deal_stage_usages_before_delete: Annotated[
        bool | None,
        "Set to true to validate deal stage usages before deleting a pipeline; false to proceed without validation.  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 'put-/crm/v3/pipelines/{objectType}/{pipelineId}_replace'.",
]:
    """Replace a specific pipeline in HubSpot CRM.

    Use this tool to replace a specific pipeline in HubSpot CRM when you need to update or modify the pipeline's entire structure or data.

    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["REPLACEPIPELINEHUBSPOT"],
            "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 object_type:
        missing_params.append(("object_type", "path"))
    if not pipeline_identifier:
        missing_params.append(("pipeline_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["REPLACEPIPELINEHUBSPOT"] + "\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["REPLACEPIPELINEHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/pipelines/{objectType}/{pipelineId}".format(  # noqa: UP032
            objectType=object_type, pipelineId=pipeline_identifier
        ),
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REPLACEPIPELINEHUBSPOT"],
        params=remove_none_values({
            "validateReferencesBeforeDelete": validate_references_before_deletion,
            "validateDealStageUsagesBeforeDelete": validate_deal_stage_usages_before_delete,
        }),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def delete_pipeline(
    context: ToolContext,
    pipeline_id: Annotated[
        str,
        "The unique identifier of the pipeline to be deleted. Required for specifying which pipeline to remove.",  # noqa: E501
    ],
    pipeline_object_type: Annotated[
        str, "Specify the type of object for the pipeline, such as 'deals' or 'tickets'."
    ],
    validate_deal_stage_usages_before_deletion: Annotated[
        bool | None,
        "Set to true to validate deal stage usages before deleting a pipeline, preventing deletion if usages are found.",  # noqa: E501
    ] = False,
    validate_references_before_delete: Annotated[
        bool | None,
        "Set to true to validate references before deleting the pipeline. This prevents accidental deletion when references are present.",  # noqa: E501
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/pipelines/{objectType}/{pipelineId}_archive'.",
]:
    """Delete a specific pipeline in the CRM.

    This tool is used to delete a specific pipeline in HubSpot CRM by providing the object type and pipeline ID. Call this tool when a pipeline needs to be removed."""  # 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/crm/v3/pipelines/{objectType}/{pipelineId}".format(  # noqa: UP032
            objectType=pipeline_object_type, pipelineId=pipeline_id
        ),
        method="DELETE",
        params=remove_none_values({
            "validateReferencesBeforeDelete": validate_references_before_delete,
            "validateDealStageUsagesBeforeDelete": validate_deal_stage_usages_before_deletion,
        }),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def update_pipeline_in_crm(
    context: ToolContext,
    crm_object_type: Annotated[
        str, "Specify the type of CRM object (e.g., deals, tickets) to update the pipeline for."
    ],
    pipeline_identifier: Annotated[
        str,
        "The unique identifier for the pipeline to be updated. This is required to specify which pipeline to modify.",  # noqa: E501
    ],
    is_pipeline_archived: Annotated[
        bool | None,
        "Set to true if the pipeline is currently archived and you intend to restore it. Use only for restoration calls.",  # noqa: E501
    ] = None,
    pipeline_display_order: Annotated[
        int | None,
        "The display order number to determine the position of the pipeline in the CRM. Pipelines with the same display order are sorted alphabetically by label.",  # noqa: E501
    ] = None,
    pipeline_label: Annotated[
        str | None, "A unique label to organize and identify the pipeline within HubSpot's UI."
    ] = None,
    validate_deal_stage_usages_before_delete: Annotated[
        bool | None,
        "Indicate if deal stage usages should be validated before deletion. A boolean value is expected.",  # noqa: E501
    ] = False,
    validate_references_before_deletion: Annotated[
        bool | None, "Set to true to validate references before deletion."
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/crm/v3/pipelines/{objectType}/{pipelineId}_update'.",
]:
    """Partially update a pipeline in the CRM.

    Use this tool to perform a partial update of a pipeline identified by its pipeline ID within a specified object type in the CRM. The tool will return the updated pipeline details."""  # noqa: E501
    request_data: Any = {
        "archived": is_pipeline_archived,
        "displayOrder": pipeline_display_order,
        "label": pipeline_label,
    }
    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/crm/v3/pipelines/{objectType}/{pipelineId}".format(  # noqa: UP032
            objectType=crm_object_type, pipelineId=pipeline_identifier
        ),
        method="PATCH",
        params=remove_none_values({
            "validateReferencesBeforeDelete": validate_references_before_deletion,
            "validateDealStageUsagesBeforeDelete": validate_deal_stage_usages_before_delete,
        }),
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_pipeline_stages(
    context: ToolContext,
    object_type: Annotated[
        str,
        "Specify the type of CRM object, such as deals or tickets, associated with the pipeline.",
    ],
    pipeline_id: Annotated[
        str,
        "The ID of the pipeline to retrieve stages for. Must be a valid pipeline ID in HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}/{pipelineId}/stages_getAll'.",  # noqa: E501
]:
    """Retrieve all stages of a specified pipeline.

    Use this tool to get all the stages associated with a specific pipeline in HubSpot CRM. It requires specifying the pipeline through its ID and object type."""  # 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/crm/v3/pipelines/{objectType}/{pipelineId}/stages".format(  # noqa: UP032
            objectType=object_type, pipelineId=pipeline_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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def create_pipeline_stage(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specify the CRM object type, such as deals or tickets, for the pipeline.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    pipeline_id: Annotated[
        str | None,
        "The unique identifier of the pipeline where the new stage will be created.  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-/crm/v3/pipelines/{objectType}/{pipelineId}/stages_create'.",  # noqa: E501
]:
    """Create a stage in a specified pipeline.

    This tool creates a new stage within a specified pipeline in the HubSpot CRM. Use it when you need to add a new stage to an existing pipeline, specifying the object type and pipeline ID.

    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["CREATEPIPELINESTAGE"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "path"))
    if not pipeline_id:
        missing_params.append(("pipeline_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["CREATEPIPELINESTAGE"] + "\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["CREATEPIPELINESTAGE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/pipelines/{objectType}/{pipelineId}/stages".format(  # noqa: UP032
            objectType=crm_object_type, pipelineId=pipeline_id
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEPIPELINESTAGE"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_pipeline_audit_log(
    context: ToolContext,
    object_type: Annotated[
        str,
        "The type of CRM object for which audit logs are being retrieved, such as 'deals' or 'contacts'.",  # noqa: E501
    ],
    pipeline_id: Annotated[
        str,
        "The unique identifier for the pipeline to fetch the audit log from. This ID is used to target a specific pipeline within HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}/{pipelineId}/audit_getAudit'.",  # noqa: E501
]:
    """Retrieves the audit log for a specified CRM pipeline.

    Call this tool to obtain a list of all changes made to a specified pipeline in HubSpot CRM, in reverse chronological order."""  # 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/crm/v3/pipelines/{objectType}/{pipelineId}/audit".format(  # noqa: UP032
            objectType=object_type, pipelineId=pipeline_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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_all_pipelines(
    context: ToolContext,
    object_type: Annotated[
        str, "Specify the CRM object type (e.g., contacts, deals) to retrieve pipelines for."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}_getAll'."
]:
    """Retrieve all pipelines for a specified object type.

    Use this tool to fetch all pipelines related to a specific object type in HubSpot CRM. This is useful for obtaining pipeline information for various CRM entities such as contacts, deals, etc."""  # 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/crm/v3/pipelines/{objectType}".format(objectType=object_type),  # 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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def create_crm_pipeline(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    pipeline_object_type: Annotated[
        str | None,
        "Specify the type of CRM object for the pipeline, such as 'deals' or 'tickets'.  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-/crm/v3/pipelines/{objectType}_create'."
]:
    """Create a new CRM pipeline in HubSpot.

    Use this tool to create a new pipeline in HubSpot CRM with given property values. It returns the entire pipeline object, including its unique ID.

    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["CREATECRMPIPELINE"],
            "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 pipeline_object_type:
        missing_params.append(("pipeline_object_type", "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["CREATECRMPIPELINE"] + "\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["CREATECRMPIPELINE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/pipelines/{objectType}".format(  # noqa: UP032
            objectType=pipeline_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECRMPIPELINE"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_pipeline_stage_by_id(
    context: ToolContext,
    object_type: Annotated[
        str,
        "The type of CRM object, such as 'deals' or 'tickets', to access the pipeline stage for.",
    ],
    pipeline_id: Annotated[
        str,
        "The unique identifier for the pipeline in HubSpot CRM. Use this ID to specify which pipeline the stage belongs to.",  # noqa: E501
    ],
    stage_id: Annotated[
        str, "Unique ID of the pipeline stage to retrieve details from HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}_getById'.",  # noqa: E501
]:
    """Retrieve a specific pipeline stage by its ID.

    Use this tool to get details of a particular stage within a pipeline in HubSpot CRM, identified by its unique stage 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/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}".format(  # noqa: UP032
            objectType=object_type, pipelineId=pipeline_id, stageId=stage_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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def replace_pipeline_stage_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specifies the CRM object type like 'deals' or 'contacts' to identify the pipeline stage being replaced.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    pipeline_id: Annotated[
        str | None,
        "The unique identifier for the pipeline whose stage properties are to be replaced. This must match the ID used in HubSpot CRM.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    pipeline_stage_id: Annotated[
        str | None,
        "The unique identifier for the pipeline stage to be replaced. This is required to specify which stage's properties will 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 'put-/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}_replace'.",  # noqa: E501
]:
    """Replace and update a pipeline stage in HubSpot CRM.

    Use this tool to replace all properties of an existing pipeline stage in HubSpot CRM with the provided values. The modified stage's details are returned in the response.

    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["REPLACEPIPELINESTAGEPROPERTIES"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "path"))
    if not pipeline_id:
        missing_params.append(("pipeline_id", "path"))
    if not pipeline_stage_id:
        missing_params.append(("pipeline_stage_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["REPLACEPIPELINESTAGEPROPERTIES"]
                + "\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["REPLACEPIPELINESTAGEPROPERTIES"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}".format(  # noqa: UP032
            objectType=crm_object_type, pipelineId=pipeline_id, stageId=pipeline_stage_id
        ),
        method="PUT",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["REPLACEPIPELINESTAGEPROPERTIES"],
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def delete_pipeline_stage(
    context: ToolContext,
    object_type: Annotated[
        str,
        "Specify the type of CRM object (e.g., deals, tickets) for which the pipeline stage is being deleted.",  # noqa: E501
    ],
    pipeline_id: Annotated[
        str, "The unique ID of the pipeline containing the stage to be deleted."
    ],
    stage_identifier: Annotated[str, "The unique identifier of the pipeline stage to be deleted."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}_archive'.",  # noqa: E501
]:
    """Deletes a pipeline stage from HubSpot CRM.

    Use to remove a specific stage from a pipeline in HubSpot CRM by specifying the object type, pipeline ID, and stage 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/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}".format(  # noqa: UP032
            objectType=object_type, pipelineId=pipeline_id, stageId=stage_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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def update_pipeline_stage(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    pipeline_object_type: Annotated[
        str | None,
        "The type of CRM object in the pipeline, such as 'deals' or 'tickets'.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    pipeline_id: Annotated[
        str | None,
        "A unique identifier for the pipeline to be updated. This is necessary to specify which pipeline contains the stage you want to modify.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    stage_id: Annotated[
        str | None,
        "The unique identifier of the stage to be updated within the pipeline. This is required to specify which stage's details need modification.  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-/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}_update'.",  # noqa: E501
]:
    """Update a stage in a CRM pipeline.

    Use this tool to update the details of a specific stage within a CRM pipeline. It is useful when you need to modify the attributes of a pipeline stage, such as its name or order, to reflect changes in the sales or business process.

    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["UPDATEPIPELINESTAGE"],
            "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 pipeline_object_type:
        missing_params.append(("pipeline_object_type", "path"))
    if not pipeline_id:
        missing_params.append(("pipeline_id", "path"))
    if not stage_id:
        missing_params.append(("stage_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["UPDATEPIPELINESTAGE"] + "\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["UPDATEPIPELINESTAGE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}".format(  # noqa: UP032
            objectType=pipeline_object_type, pipelineId=pipeline_id, stageId=stage_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPIPELINESTAGE"],
        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=["crm.objects.contacts.read"]))
async def get_multiple_postal_mail_objects(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    include_archived: Annotated[
        bool | None,
        "Set to true to include archived postal mail objects in the results.  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-/crm/v3/objects/postal_mail/batch/read_read'.",
]:
    """Retrieve multiple postal mail objects by IDs or unique values.

    This tool retrieves multiple postal mail objects from HubSpot CRM using their internal IDs or unique property values. It is useful when you need to access detailed information on several postal mail entries at once.

    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["GETMULTIPLEPOSTALMAILOBJECTS"],
            "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["GETMULTIPLEPOSTALMAILOBJECTS"]
                + "\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["GETMULTIPLEPOSTALMAILOBJECTS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["GETMULTIPLEPOSTALMAILOBJECTS"],
        params=remove_none_values({"archived": include_archived}),
        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=["crm.objects.contacts.write"]))
async def archive_postal_mail_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-/crm/v3/objects/postal_mail/batch/archive_archive'.",
]:
    """Archive a batch of postal mail objects using their IDs.

    Use this tool to archive multiple postal mail objects in HubSpot CRM by providing their IDs. This is useful for managing large volumes of mailing data efficiently.

    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["ARCHIVEPOSTALMAILBATCH"],
            "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["ARCHIVEPOSTALMAILBATCH"] + "\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["ARCHIVEPOSTALMAILBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEPOSTALMAILBATCH"],
        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=["crm.objects.contacts.read"]))
async def get_postal_mail_records(
    context: ToolContext,
    associated_objects: Annotated[
        list[str] | None,
        "A list of object types to retrieve associations for, such as contacts or companies.",
    ] = None,
    include_archived_records: Annotated[
        bool | None, "Include archived postal mail records if true; exclude them if false."
    ] = False,
    include_properties_history: Annotated[
        list[str] | None,
        "Specify property names to include their history in the records. Use an array of strings.",
    ] = None,
    max_records: Annotated[
        int | None, "The maximum number of postal mail records to return. Must be an integer."
    ] = 10,
    pagination_cursor_after: Annotated[
        str | None,
        "A cursor for pagination. Use it to retrieve the next set of postal mail records after a specific point.",  # noqa: E501
    ] = None,
    retrieve_specific_properties: Annotated[
        list[str] | None,
        "List of specific properties to include in the response. Provide property names as strings.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/postal_mail_getPage'."
]:
    """Retrieve postal mail records from the CRM.

    Use this tool to access postal mail records from the CRM. It is helpful for obtaining stored postal mail information."""  # 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/crm/v3/objects/postal_mail",
        method="GET",
        params=remove_none_values({
            "limit": max_records,
            "after": pagination_cursor_after,
            "properties": retrieve_specific_properties,
            "propertiesWithHistory": include_properties_history,
            "associations": associated_objects,
            "archived": include_archived_records,
        }),
        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=["crm.objects.contacts.write"]))
async def create_postal_mail_object(
    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-/crm/v3/objects/postal_mail_create'."
]:
    """Create a postal mail object in HubSpot CRM.

    This tool is used to create a postal mail object in the HubSpot CRM with the provided properties. It returns a copy of the created object, including its unique identifier. Use this tool to record postal mail interactions in your CRM.

    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["CREATEPOSTALMAILOBJECT"],
            "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["CREATEPOSTALMAILOBJECT"] + "\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["CREATEPOSTALMAILOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEPOSTALMAILOBJECT"],
        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=["crm.objects.contacts.write"]))
async def create_postal_mail_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-/crm/v3/objects/postal_mail/batch/create_create'.",
]:
    """Create a batch of postal mail objects in HubSpot CRM.

    Use this tool to create multiple postal mail records within the HubSpot CRM system. Ideal for handling bulk postal mail operations efficiently.

    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["CREATEPOSTALMAILBATCH"],
            "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["CREATEPOSTALMAILBATCH"] + "\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["CREATEPOSTALMAILBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEPOSTALMAILBATCH"],
        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=["crm.objects.contacts.write"]))
async def upsert_postal_mail_in_hubspot(
    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-/crm/v3/objects/postal_mail/batch/upsert_upsert'.",
]:
    """Create or update postal mail records in HubSpot CRM.

    This tool creates or updates postal mail records in HubSpot CRM based on a unique property value specified by the `idProperty` query parameter. Use it to ensure postal mail records are up-to-date and accurately maintained.

    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["UPSERTPOSTALMAILINHUBSPOT"],
            "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["UPSERTPOSTALMAILINHUBSPOT"] + "\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["UPSERTPOSTALMAILINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTPOSTALMAILINHUBSPOT"],
        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=["crm.objects.contacts.read"]))
async def get_postal_mail_by_id(
    context: ToolContext,
    postal_mail_id: Annotated[
        str,
        "The unique identifier for the postal mail record to retrieve details from HubSpot CRM.",
    ],
    identify_by_id_property: Annotated[
        str | None,
        "Specifies which property will be used as the primary identifier. Accepts a string that represents the property within the postal mail object that should be used to identify and retrieve details.",  # noqa: E501
    ] = None,
    include_archived: Annotated[
        bool | None, "Set to true to include archived postal mail records in the response."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names for which history should be returned. Accepts an array of strings.",  # noqa: E501
    ] = None,
    related_objects_associations: Annotated[
        list[str] | None,
        "List of string identifiers representing related objects to retrieve alongside the postal mail record.",  # noqa: E501
    ] = None,
    specified_properties: Annotated[
        list[str] | None,
        "A list of specific postal mail properties to retrieve. Leave empty to get all properties.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/postal_mail/{postalMailId}_getById'.",
]:
    """Retrieve details of a postal mail record by ID from HubSpot CRM.

    Use this tool to get detailed information about a specific postal mail record in HubSpot CRM by providing the postal mail 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/crm/v3/objects/postal_mail/{postalMailId}".format(  # noqa: UP032
            postalMailId=postal_mail_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": specified_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": related_objects_associations,
            "archived": include_archived,
            "idProperty": identify_by_id_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=["crm.objects.contacts.write"]))
async def archive_postal_mail(
    context: ToolContext,
    postal_mail_id: Annotated[
        str, "The unique identifier of the postal mail object to be archived."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/postal_mail/{postalMailId}_archive'.",
]:
    """Archive a postal mail object in HubSpot CRM.

    This tool moves a postal mail object, specified by its ID, to the recycling bin in HubSpot CRM. Use this tool when you need to archive a specific postal mail entry."""  # 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/crm/v3/objects/postal_mail/{postalMailId}".format(  # noqa: UP032
            postalMailId=postal_mail_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=["crm.objects.contacts.write"]))
async def update_postal_mail_record(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    postal_mail_id: Annotated[
        str | None,
        "A unique identifier for the postal mail record to be updated in HubSpot CRM.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    property_identifier: Annotated[
        str | None,
        "Specify the property key of the postal mail record to identify which field to update. This is typically the name of the field in the CRM record.  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-/crm/v3/objects/postal_mail/{postalMailId}_update'.",
]:
    """Update a postal mail record in HubSpot CRM.

    Use this tool to update an existing postal mail record in HubSpot CRM by specifying the postalMailId of the record to be updated.

    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["UPDATEPOSTALMAILRECORD"],
            "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 postal_mail_id:
        missing_params.append(("postal_mail_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["UPDATEPOSTALMAILRECORD"] + "\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["UPDATEPOSTALMAILRECORD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/{postalMailId}".format(  # noqa: UP032
            postalMailId=postal_mail_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPOSTALMAILRECORD"],
        params=remove_none_values({"idProperty": property_identifier}),
        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=["crm.objects.contacts.write"]))
async def update_multiple_postal_mails(
    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-/crm/v3/objects/postal_mail/batch/update_update'.",
]:
    """Update multiple postal mail records at once in HubSpot CRM.

    Use this tool to update several postal mail objects within HubSpot CRM in a single request, streamlining batch modifications.

    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["UPDATEMULTIPLEPOSTALMAILS"],
            "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["UPDATEMULTIPLEPOSTALMAILS"] + "\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["UPDATEMULTIPLEPOSTALMAILS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLEPOSTALMAILS"],
        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=["crm.objects.contacts.read"]))
async def search_postal_mail_hubspot(
    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-/crm/v3/objects/postal_mail/search_doSearch'.",
]:
    """Search for postal mail objects in HubSpot CRM.

    Use this tool to search for postal mail records in HubSpot CRM based on specific criteria. This is useful when you need to retrieve details about postal interactions stored within the CRM.

    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["SEARCHPOSTALMAILHUBSPOT"],
            "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["SEARCHPOSTALMAILHUBSPOT"] + "\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["SEARCHPOSTALMAILHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/postal_mail/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHPOSTALMAILHUBSPOT"],
        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=["crm.objects.products.write"]))
async def archive_products_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-/crm/v3/objects/products/batch/archive_archive'.",
]:
    """Archive a batch of products by ID in HubSpot CRM.

    Use this tool to archive multiple products in HubSpot CRM by providing their IDs. It should be called when you need to bulk remove products from the CRM database.

    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["ARCHIVEPRODUCTSBATCH"],
            "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["ARCHIVEPRODUCTSBATCH"] + "\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["ARCHIVEPRODUCTSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEPRODUCTSBATCH"],
        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=["crm.objects.products.read"]))
async def get_products_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "Provide a comma-separated list of object types to retrieve associated IDs for. Ignored if not existing.",  # noqa: E501
    ] = None,
    include_properties_with_history: Annotated[
        list[str] | None,
        "Comma separated list of product properties to include along with their history. Reduces maximum results per request if used.",  # noqa: E501
    ] = None,
    maximum_results_per_page: Annotated[
        int | None, "The maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None, "The cursor token for the last read resource, used for paged responses."
    ] = None,
    product_properties_to_return: Annotated[
        list[str] | None,
        "Comma-separated properties to include in the response. Any non-existent properties for the requested objects will be ignored.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/products_getPage'."
]:
    """Fetch a page of products from HubSpot CRM.

    Use this tool to retrieve a page of products from the HubSpot CRM. You can control the returned product details using the `properties` 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/crm/v3/objects/products",
        method="GET",
        params=remove_none_values({
            "limit": maximum_results_per_page,
            "after": paging_cursor_token,
            "properties": product_properties_to_return,
            "propertiesWithHistory": include_properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.products.write"]))
async def create_product_in_hubspot(
    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-/crm/v3/objects/products_create'."
]:
    """Create a new product in HubSpot CRM.

    Use this tool to create a product in HubSpot CRM with specified properties. It returns the created product details, including the ID, which can be used for further referencing or operations.

    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["CREATEPRODUCTINHUBSPOT"],
            "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["CREATEPRODUCTINHUBSPOT"] + "\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["CREATEPRODUCTINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEPRODUCTINHUBSPOT"],
        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=["crm.objects.products.read"]))
async def search_hubspot_products(
    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-/crm/v3/objects/products/search_doSearch'.",
]:
    """Search for products in HubSpot CRM.

    Use this tool to search for and retrieve product information from HubSpot CRM. It calls the endpoint designed to query products based on specified criteria.

    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["SEARCHHUBSPOTPRODUCTS"],
            "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["SEARCHHUBSPOTPRODUCTS"] + "\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["SEARCHHUBSPOTPRODUCTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTPRODUCTS"],
        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=["crm.objects.products.read"]))
async def retrieve_hubspot_product_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    only_archived: Annotated[
        bool | None,
        "Set to true to retrieve only archived product records. False returns unarchived records.  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-/crm/v3/objects/products/batch/read_read'.",
]:
    """Retrieve HubSpot product records by ID or unique property.

    This tool retrieves HubSpot CRM product records using a record ID or a custom unique value property (`idProperty`). It is useful for accessing detailed product data stored in HubSpot CRM based on specific identifiers.

    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["RETRIEVEHUBSPOTPRODUCTRECORDS"],
            "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["RETRIEVEHUBSPOTPRODUCTRECORDS"]
                + "\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["RETRIEVEHUBSPOTPRODUCTRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTPRODUCTRECORDS"],
        params=remove_none_values({"archived": only_archived}),
        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=["crm.objects.products.write"]))
async def create_batch_of_products(
    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-/crm/v3/objects/products/batch/create_create'.",
]:
    """Create a batch of products in HubSpot CRM.

    Use this tool to add multiple products to the HubSpot CRM in a single batch. Ideal for updating product catalogs efficiently.

    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["CREATEBATCHOFPRODUCTS"],
            "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["CREATEBATCHOFPRODUCTS"] + "\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["CREATEBATCHOFPRODUCTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFPRODUCTS"],
        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=["crm.objects.products.write"]))
async def upsert_hubspot_products_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-/crm/v3/objects/products/batch/upsert_upsert'.",
]:
    """Batch create or update HubSpot product records.

    This tool is used to create or update HubSpot product records in bulk, identified by a unique property value specified by the `idProperty`. It should be called when there's a need to synchronize or modify multiple product entries in the HubSpot CRM.

    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["UPSERTHUBSPOTPRODUCTSBATCH"],
            "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["UPSERTHUBSPOTPRODUCTSBATCH"]
                + "\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["UPSERTHUBSPOTPRODUCTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTPRODUCTSBATCH"],
        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=["crm.objects.products.read"]))
async def get_product_details_by_id(
    context: ToolContext,
    product_id: Annotated[
        str,
        "A unique identifier for the product. Can be the internal ID or any unique property as specified by `idProperty`.",  # noqa: E501
    ],
    properties_to_return: Annotated[
        list[str] | None,
        "A list of product properties to include in the response. Any missing properties will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List properties to return with their history of past values, separated by commas. Ignored if not present in object.",  # noqa: E501
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Any non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived results. If false, only non-archived results are returned.",  # noqa: E501
    ] = False,
    unique_property_name: Annotated[
        str | None,
        "Specify the property name with unique values for the product object, instead of default productId.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/products/{productId}_getById'.",
]:
    """Retrieve product details using a product ID.

    This tool retrieves the details of a product in HubSpot CRM using its product ID. It can also use any unique property specified by the `idProperty` query parameter. Use this tool to obtain specific product information from HubSpot CRM."""  # 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/crm/v3/objects/products/{productId}".format(  # noqa: UP032
            productId=product_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "archived": return_archived_only,
            "idProperty": unique_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=["crm.objects.products.write"]))
async def remove_product(
    context: ToolContext,
    product_id: Annotated[
        str, "The unique identifier of the product to be archived in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/products/{productId}_archive'.",
]:
    """Archive a product by moving it to the recycling bin.

    Use this tool to move a product, identified by its productId, to the recycling bin in HubSpot CRM."""  # 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/crm/v3/objects/products/{productId}".format(  # noqa: UP032
            productId=product_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=["crm.objects.products.write"]))
async def update_product_info(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    product_id: Annotated[
        str | None,
        "The internal object ID of the product to be updated. This identifies the specific product in HubSpot CRM for the update operation.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The unique property name used to identify the product. It should be a string representing a property with unique values.  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-/crm/v3/objects/products/{productId}_update'.",
]:
    """Partially update product information in HubSpot CRM.

    This tool updates specific properties of a product identified by `productId` in HubSpot CRM. It will override existing property values, and returning errors for read-only or non-existent properties. Use when you need to modify product attributes such as name, price, or description.

    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["UPDATEPRODUCTINFO"],
            "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 product_id:
        missing_params.append(("product_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["UPDATEPRODUCTINFO"] + "\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["UPDATEPRODUCTINFO"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/{productId}".format(  # noqa: UP032
            productId=product_id
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPRODUCTINFO"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.products.write"]))
async def update_hubspot_products_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-/crm/v3/objects/products/batch/update_update'.",
]:
    """Update a batch of HubSpot products by ID or unique properties.

    Use this tool to update multiple products in HubSpot CRM with new details by internal IDs or unique property values.

    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["UPDATEHUBSPOTPRODUCTSBATCH"],
            "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["UPDATEHUBSPOTPRODUCTSBATCH"]
                + "\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["UPDATEHUBSPOTPRODUCTSBATCH"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/products/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTPRODUCTSBATCH"],
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def archive_crm_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specify the type of CRM object (e.g., 'contacts', 'companies') for which the properties should be archived.  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-/crm/v3/properties/{objectType}/batch/archive'.",
]:
    """Archive a list of properties in HubSpot CRM.

    Use this tool to archive a provided list of properties within a specified object type in HubSpot CRM. It returns a confirmation of successful archiving, regardless of the properties' initial state.

    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["ARCHIVECRMPROPERTIES"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["ARCHIVECRMPROPERTIES"] + "\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["ARCHIVECRMPROPERTIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/properties/{objectType}/batch/archive".format(  # noqa: UP032
            objectType=crm_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVECRMPROPERTIES"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def read_property_group(
    context: ToolContext,
    object_type: Annotated[str, "Specify the type of CRM object, such as 'contacts' or 'deals'."],
    property_group_name: Annotated[str, "The name of the property group to be retrieved."],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/properties/{objectType}/groups/{groupName}'.",
]:
    """Retrieve details of a property group by its name.

    Use this tool to get detailed information about a property group identified by {groupName} within a specified object type in HubSpot CRM."""  # 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/crm/v3/properties/{objectType}/groups/{groupName}".format(  # noqa: UP032
            objectType=object_type, groupName=property_group_name
        ),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def delete_property_group(
    context: ToolContext,
    crm_object_type: Annotated[
        str, "Specify the type of CRM object, such as 'contacts', 'companies', etc."
    ],
    property_group_name: Annotated[
        str,
        "The name of the property group to delete. This identifies which group to move to the recycling bin in HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/properties/{objectType}/groups/{groupName}'.",
]:
    """Delete a property group and move it to recycling bin.

    Use this tool to move a specified property group to the recycling bin in HubSpot CRM. Ideal when a property group is no longer needed and should be removed."""  # 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/crm/v3/properties/{objectType}/groups/{groupName}".format(  # noqa: UP032
            objectType=crm_object_type, groupName=property_group_name
        ),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def update_property_group(
    context: ToolContext,
    object_type: Annotated[
        str, "Specifies the type of object in HubSpot CRM (e.g., contacts, companies)."
    ],
    property_group_name: Annotated[
        str, "The unique name of the property group to be updated in HubSpot CRM."
    ],
    property_group_display_order: Annotated[
        int | None,
        "Set the display order of the property group. Use positive integers for ordering, or -1 to display after positive values.",  # noqa: E501
    ] = None,
    property_group_label: Annotated[
        str | None, "A human-readable label for the property group in HubSpot."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/crm/v3/properties/{objectType}/groups/{groupName}'.",
]:
    """Update fields in a specified property group.

    This tool performs a partial update on a property group identified by {groupName} in HubSpot CRM. It overwrites the provided fields in the property group. Useful for modifying specific attributes within a property group without altering the entire group."""  # noqa: E501
    request_data: Any = {
        "displayOrder": property_group_display_order,
        "label": property_group_label,
    }
    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/crm/v3/properties/{objectType}/groups/{groupName}".format(  # noqa: UP032
            objectType=object_type, groupName=property_group_name
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(
    requires_auth=OAuth2(
        id="arcade-hubspot",
        scopes=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def read_property(
    context: ToolContext,
    crm_object_type: Annotated[
        str,
        "Specify the CRM object type, such as 'contact' or 'company', to retrieve the property for.",  # noqa: E501
    ],
    property_name: Annotated[
        str,
        "The unique name of the property to retrieve. This should match the property name in HubSpot CRM.",  # noqa: E501
    ],
    property_specifications: Annotated[
        str | None,
        "Specify the details or attributes of the property to retrieve. Use a comma-separated list for multiple specifications.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to True to return only archived property results."
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/properties/{objectType}/{propertyName}'.",
]:
    """Retrieve CRM property details by name and type.

    This tool fetches details of a specific property from HubSpot CRM by providing the property name and object type. Use this tool to access property information when dealing with HubSpot CRM 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/crm/v3/properties/{objectType}/{propertyName}".format(  # noqa: UP032
            objectType=crm_object_type, propertyName=property_name
        ),
        method="GET",
        params=remove_none_values({
            "archived": return_archived_only,
            "properties": property_specifications,
        }),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def delete_property_hubspot_crm(
    context: ToolContext,
    object_type: Annotated[
        str, "Specify the type of object in HubSpot CRM (e.g., 'contacts', 'companies')."
    ],
    property_name: Annotated[
        str,
        "The name of the property to delete, identified by its unique name within the object type.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/properties/{objectType}/{propertyName}'.",
]:
    """Delete a property in HubSpot CRM and move it to the recycling bin.

    Use this tool to delete a specific property from the HubSpot CRM by specifying the object type and property name. This action moves the property to the recycling bin."""  # 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/crm/v3/properties/{objectType}/{propertyName}".format(  # noqa: UP032
            objectType=object_type, propertyName=property_name
        ),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def update_property_value(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "Specify the type of CRM object (e.g., 'contacts', 'deals') to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    property_identifier: Annotated[
        str | None,
        "The unique name of the CRM property 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-/crm/v3/properties/{objectType}/{propertyName}'.",
]:
    """Update specific fields of a CRM property partially.

    Use this tool to partially update specific fields of a CRM property identified by its name. Only the specified fields will be overwritten.

    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["UPDATEPROPERTYVALUE"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "path"))
    if not property_identifier:
        missing_params.append(("property_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["UPDATEPROPERTYVALUE"] + "\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["UPDATEPROPERTYVALUE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/properties/{objectType}/{propertyName}".format(  # noqa: UP032
            objectType=crm_object_type, propertyName=property_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPROPERTYVALUE"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def read_batch_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    crm_object_type: Annotated[
        str | None,
        "The type of CRM object for which properties are being read (e.g., contacts, deals).  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-/crm/v3/properties/{objectType}/batch/read'.",
]:
    """Fetches a batch of properties for a specified CRM object type.

    Use this tool to retrieve a list of properties for a given object type in the CRM. This can be useful when you need detailed information about multiple properties at once.

    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["READBATCHPROPERTIES"],
            "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 crm_object_type:
        missing_params.append(("crm_object_type", "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["READBATCHPROPERTIES"] + "\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["READBATCHPROPERTIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/properties/{objectType}/batch/read".format(  # noqa: UP032
            objectType=crm_object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["READBATCHPROPERTIES"],
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def create_batch_properties(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "Specifies the type of CRM object for which to create properties (e.g., contacts, deals, companies).  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-/crm/v3/properties/{objectType}/batch/create'.",
]:
    """Create a batch of properties for a specified object type in HubSpot.

    This tool facilitates the creation of multiple properties at once for any specified object type in HubSpot CRM, using the standard rules applied to individual property creation.

    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["CREATEBATCHPROPERTIES"],
            "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 object_type:
        missing_params.append(("object_type", "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["CREATEBATCHPROPERTIES"] + "\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["CREATEBATCHPROPERTIES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/properties/{objectType}/batch/create".format(  # noqa: UP032
            objectType=object_type
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHPROPERTIES"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def retrieve_hubspot_properties(
    context: ToolContext,
    object_type: Annotated[
        str, "Specifies the HubSpot object type (e.g., contact, deal) to retrieve properties for."
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only results that have been archived. Otherwise, return all properties.",  # noqa: E501
    ] = False,
    selected_properties: Annotated[
        str | None,
        "A comma-separated list of specific properties to retrieve for the object type. Leave empty to retrieve all properties.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/properties/{objectType}'."
]:
    """Retrieve all properties for a HubSpot object type.

    Use this tool to read all existing properties for a specified object type in HubSpot CRM. It provides property details relevant to the chosen object type within a HubSpot account."""  # 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/crm/v3/properties/{objectType}".format(objectType=object_type),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "archived": return_archived_only,
            "properties": selected_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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def create_hubspot_crm_property(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_type: Annotated[
        str | None,
        "Specify the object type to which the new property will be added, such as contact, company, or deal.  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-/crm/v3/properties/{objectType}'."
]:
    """Create a new property for a specified object type in HubSpot CRM.

    This tool creates and returns a new property for the specified object type in HubSpot CRM. It should be used when you need to add a custom property to an object such as a contact, company, or deal in HubSpot.

    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["CREATEHUBSPOTCRMPROPERTY"],
            "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 object_type:
        missing_params.append(("object_type", "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["CREATEHUBSPOTCRMPROPERTY"] + "\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["CREATEHUBSPOTCRMPROPERTY"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/properties/{objectType}".format(objectType=object_type),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTCRMPROPERTY"],
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def retrieve_hubspot_property_groups(
    context: ToolContext,
    hubspot_object_type: Annotated[
        str,
        "Specify the HubSpot object type to retrieve property groups, such as 'contacts', 'companies', or 'deals'.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/properties/{objectType}/groups'."
]:
    """Retrieve HubSpot CRM property groups for a specified object type.

    This tool retrieves all existing property groups for a specified object type within a HubSpot account. Use it to access property group information for organizing and managing CRM data effectively."""  # 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/crm/v3/properties/{objectType}/groups".format(  # noqa: UP032
            objectType=hubspot_object_type
        ),
        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=[
            "crm.schemas.carts.write",
            "crm.schemas.invoices.write",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.write",
            "crm.schemas.services.write",
            "e-commerce",
            "crm.objects.orders.write",
            "crm.schemas.deals.write",
            "tickets.highly_sensitive.v2",
            "tickets",
            "crm.objects.carts.write",
            "crm.schemas.courses.write",
            "crm.schemas.custom.write",
            "crm.schemas.commercepayments.write",
            "crm.objects.users.write",
            "crm.schemas.companies.write",
            "crm.schemas.orders.write",
            "crm.schemas.appointments.write",
            "crm.pipelines.orders.write",
            "crm.schemas.subscriptions.write",
            "crm.schemas.listings.write",
        ],
    )
)
async def create_property_group(
    context: ToolContext,
    internal_property_group_name: Annotated[
        str, "The unique name used internally to reference the property group via the API."
    ],
    object_type: Annotated[
        str, "Specifies the CRM object type for the property group (e.g., contacts, companies)."
    ],
    property_group_label: Annotated[
        str, "A human-readable label for the property group, displayed in HubSpot."
    ],
    property_group_display_order: Annotated[
        int | None,
        "Defines the display order of the property group, with lowest positive integers displayed first. Use -1 to display after positive values.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/properties/{objectType}/groups'."
]:
    """Create a new property group in HubSpot CRM.

    This tool creates and returns a new property group for a specified object type in HubSpot CRM. Use it when you need to organize CRM properties into groups."""  # noqa: E501
    request_data: Any = {
        "name": internal_property_group_name,
        "displayOrder": property_group_display_order,
        "label": property_group_label,
    }
    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/crm/v3/properties/{objectType}/groups".format(  # noqa: UP032
            objectType=object_type
        ),
        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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def get_property_validation_rules(
    context: ToolContext,
    object_type_id: Annotated[
        str,
        "The unique identifier for the object type in HubSpot CRM whose property validation rules you want to retrieve. This is a string value.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/property-validations/{objectTypeId}'.",
]:
    """Retrieve validation rules for properties of a given object in HubSpot CRM.

    Use this tool to get detailed validation rules for all properties associated with a specified object type ID in HubSpot CRM. This can be helpful for understanding constraints and rules applied to object properties."""  # 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/crm/v3/property-validations/{objectTypeId}".format(  # noqa: UP032
            objectTypeId=object_type_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=[
            "crm.objects.carts.read",
            "crm.objects.deals.read",
            "crm.objects.custom.write",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.subscriptions.write",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.pipelines.orders.read",
            "automation",
            "crm.objects.companies.highly_sensitive.write.v2",
            "crm.objects.appointments.write",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.marketing_events.read",
            "crm.objects.appointments.read",
            "crm.schemas.appointments.read",
            "crm.objects.subscriptions.read",
            "crm.schemas.deals.read",
            "crm.objects.invoices.write",
            "crm.objects.line_items.read",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.schemas.line_items.read",
            "crm.objects.goals.read",
            "media_bridge.read",
            "crm.objects.products.write",
            "crm.schemas.listings.read",
            "crm.objects.contacts.write",
            "crm.objects.goals.write",
            "crm.schemas.custom.read",
            "crm.objects.marketing_events.write",
            "crm.objects.leads.read",
            "crm.schemas.orders.read",
            "crm.objects.feedback_submissions.read",
            "crm.objects.custom.sensitive.write.v2",
            "crm.schemas.quotes.read",
            "tickets",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.schemas.commercepayments.read",
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.objects.users.read",
            "crm.objects.contacts.read",
            "crm.objects.deals.write",
            "crm.objects.services.read",
            "crm.objects.custom.highly_sensitive.write.v2",
            "crm.objects.listings.read",
            "crm.objects.leads.write",
            "crm.objects.commercepayments.write",
            "crm.objects.orders.read",
            "crm.schemas.carts.read",
            "tickets.sensitive.v2",
            "crm.schemas.invoices.read",
            "crm.objects.contacts.sensitive.write.v2",
            "e-commerce",
            "crm.objects.owners.read",
            "crm.objects.quotes.write",
            "crm.objects.line_items.write",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.schemas.services.read",
            "crm.objects.courses.write",
            "crm.schemas.contacts.read",
            "crm.objects.appointments.sensitive.write.v2",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.services.write",
            "crm.objects.courses.read",
            "crm.schemas.courses.read",
            "crm.objects.custom.read",
            "crm.schemas.subscriptions.read",
            "crm.schemas.companies.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.companies.write",
            "crm.objects.quotes.read",
            "crm.objects.companies.read",
            "crm.objects.custom.sensitive.read.v2",
            "crm.objects.invoices.read",
            "crm.objects.listings.write",
            "crm.objects.appointments.sensitive.read.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def fetch_property_validation(
    context: ToolContext,
    object_type_id: Annotated[
        str,
        'The unique identifier for the object type in HubSpot CRM, such as "contacts" or "deals".',
    ],
    property_name: Annotated[
        str,
        "The name of the property whose validation rules you want to retrieve in HubSpot CRM. It must match exactly to identify the property correctly.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/property-validations/{objectTypeId}/{propertyName}'.",  # noqa: E501
]:
    """Retrieve validation rules for a specific property in HubSpot CRM.

    This tool is used to read the validation rules of a property identified by {propertyName} within a specified object type in HubSpot CRM. It helps to understand the constraints and validations applied to a property."""  # 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/crm/v3/property-validations/{objectTypeId}/{propertyName}".format(  # noqa: UP032
            objectTypeId=object_type_id, propertyName=property_name
        ),
        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=["crm.objects.quotes.write"]))
async def archive_quotes_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-/crm/v3/objects/quotes/batch/archive_archive'.",
]:
    """Archive a batch of quotes in HubSpot CRM.

    Use this tool to archive multiple quotes in HubSpot CRM by providing their IDs. It should be called when there's a need to bulk archive quotes to manage CRM records efficiently.

    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["ARCHIVEQUOTESBATCH"],
            "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["ARCHIVEQUOTESBATCH"] + "\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["ARCHIVEQUOTESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEQUOTESBATCH"],
        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=["crm.objects.quotes.read"]))
async def get_quotes_page(
    context: ToolContext,
    paging_cursor_token: Annotated[
        str | None,
        "The token to identify the last read resource for pagination. Use it to get the next page of results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history. Reduces max quotes per request.",
    ] = None,
    quote_properties_to_return: Annotated[
        list[str] | None,
        "List the properties to retrieve for each quote. Only present properties will be returned.",
    ] = None,
    results_limit: Annotated[
        int | None,
        "The maximum number of quote results to display per page. Accepts an integer value.",
    ] = 10,
    retrieve_associated_ids_for_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Non-existing associations will be ignored.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None, "Set to true to return only archived results. False to include active results."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/quotes_getPage'."
]:
    """Retrieve a page of quotes with specified properties.

    Use this tool to read a page of quotes from HubSpot CRM. You can control the details returned by specifying properties through query parameters."""  # 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/crm/v3/objects/quotes",
        method="GET",
        params=remove_none_values({
            "limit": results_limit,
            "after": paging_cursor_token,
            "properties": quote_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_ids_for_object_types,
            "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=["crm.objects.quotes.write"]))
async def create_hubspot_quote(
    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-/crm/v3/objects/quotes_create'."
]:
    """Create a new quote in HubSpot CRM.

    Use this tool to create a new quote in HubSpot CRM with specified properties. It returns the created quote, including its ID.

    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["CREATEHUBSPOTQUOTE"],
            "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["CREATEHUBSPOTQUOTE"] + "\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["CREATEHUBSPOTQUOTE"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTQUOTE"],
        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=["crm.objects.quotes.read"]))
async def get_quote_by_id(
    context: ToolContext,
    quote_id: Annotated[
        str,
        "The ID or unique property value of the quote to be retrieved. This identifies the specific quote in HubSpot CRM.",  # noqa: E501
    ],
    included_properties: Annotated[
        list[str] | None,
        "List of properties to be returned in the response. If a property is not present, it will be ignored. Input should be an array of strings.",  # noqa: E501
    ] = None,
    only_return_archived: Annotated[
        bool | None, "Set to true to only return results that have been archived for the quote."
    ] = False,
    properties_with_history: Annotated[
        list[str] | None,
        "Comma-separated list of properties to return with their value history. Ignored if not present.",  # noqa: E501
    ] = None,
    retrieve_associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for; ignored if non-existent.",
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The property name with unique values for the quote object, used in the retrieval process.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/quotes/{quoteId}_getById'."
]:
    """Retrieve details of a quote by its ID.

    Fetches a quote from HubSpot CRM using its ID or a unique property value. Allows control over returned properties."""  # 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/crm/v3/objects/quotes/{quoteId}".format(quoteId=quote_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": included_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_types,
            "archived": only_return_archived,
            "idProperty": unique_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=["crm.objects.quotes.write"]))
async def archive_quote(
    context: ToolContext,
    quote_identifier: Annotated[
        str, "The unique identifier for the quote to be archived in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/quotes/{quoteId}_archive'.",
]:
    """Archive a quote by moving it to the recycling bin.

    This tool archives a quote in HubSpot CRM, identified by its `quoteId`, by moving it to the recycling bin. Use this tool when you need to delete a quote temporarily."""  # 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/crm/v3/objects/quotes/{quoteId}".format(  # noqa: UP032
            quoteId=quote_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=["crm.objects.quotes.write"]))
async def update_quote_information(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    quote_identifier: Annotated[
        str | None,
        "The identifier of the quote to be updated. This can be the internal ID or a unique property value specified by `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property with unique values for identifying the quote object. Used instead of `quoteId`.  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-/crm/v3/objects/quotes/{quoteId}_update'.",
]:
    """Update a quote's details in HubSpot CRM.

    Use this tool to perform a partial update of a quote in HubSpot CRM identified by `quoteId` or a unique property value. It allows overwriting specific property values, but read-only or invalid properties will cause an error. Properties can be cleared by passing an empty string.

    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["UPDATEQUOTEINFORMATION"],
            "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 quote_identifier:
        missing_params.append(("quote_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["UPDATEQUOTEINFORMATION"] + "\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["UPDATEQUOTEINFORMATION"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/{quoteId}".format(  # noqa: UP032
            quoteId=quote_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEQUOTEINFORMATION"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.quotes.write"]))
async def update_quote_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-/crm/v3/objects/quotes/batch/update_update'.",
]:
    """Update a batch of quotes using internal ID or property values.

    Use this tool to update multiple quotes at once in the CRM. It should be called when you need to modify details of several quotes by their internal IDs or unique property values.

    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["UPDATEQUOTEBATCH"],
            "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["UPDATEQUOTEBATCH"] + "\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["UPDATEQUOTEBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEQUOTEBATCH"],
        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=["crm.objects.quotes.write"]))
async def create_or_update_quotes(
    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-/crm/v3/objects/quotes/batch/upsert_upsert'.",
]:
    """Create or update quote records in HubSpot CRM.

    This tool is used to create or update quote records in HubSpot CRM based on a unique property value specified by `idProperty`. Use it when you need to ensure quotes are updated or added to the CRM system efficiently.

    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["CREATEORUPDATEQUOTES"],
            "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["CREATEORUPDATEQUOTES"] + "\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["CREATEORUPDATEQUOTES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATEQUOTES"],
        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=["crm.objects.quotes.write"]))
async def create_batch_of_quotes(
    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-/crm/v3/objects/quotes/batch/create_create'.",
]:
    """Creates a batch of quotes in HubSpot CRM.

    This tool is used to create multiple quotes at once using HubSpot CRM's batch creation endpoint. Ideal for automating the quote generation process for multiple clients or deals.

    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["CREATEBATCHOFQUOTES"],
            "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["CREATEBATCHOFQUOTES"] + "\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["CREATEBATCHOFQUOTES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFQUOTES"],
        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=["crm.objects.quotes.read"]))
async def search_quotes_in_hubspot(
    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-/crm/v3/objects/quotes/search_doSearch'."
]:
    """Search for quotes in HubSpot CRM.

    Use this tool to perform a search for quotes stored in HubSpot CRM. Useful for retrieving information about sales quotes based on specific criteria or parameters.

    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["SEARCHQUOTESINHUBSPOT"],
            "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["SEARCHQUOTESINHUBSPOT"] + "\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["SEARCHQUOTESINHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHQUOTESINHUBSPOT"],
        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=["crm.objects.quotes.read"]))
async def retrieve_quotes_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    archived_results_only: Annotated[
        bool | None,
        "Specify `true` to return only archived results; `false` to include non-archived results.  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-/crm/v3/objects/quotes/batch/read_read'."
]:
    """Retrieve multiple quotes by ID or custom property.

    This tool retrieves multiple quote records by their IDs or a custom unique value property. It should be called when you need to access specific quotes in batch format from the HubSpot CRM.

    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["RETRIEVEQUOTESBATCH"],
            "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["RETRIEVEQUOTESBATCH"] + "\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["RETRIEVEQUOTESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/quotes/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEQUOTESBATCH"],
        params=remove_none_values({"archived": archived_results_only}),
        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=[
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.custom.sensitive.read.v2",
            "crm.schemas.custom.read",
            "crm.objects.custom.read",
        ],
    )
)
async def get_hubspot_crm_object_schemas(
    context: ToolContext,
    return_archived_only: Annotated[
        bool | None, "Set to True to return only results that have been archived."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm-object-schemas/v3/schemas_getAll'."
]:
    """Retrieve HubSpot CRM object schemas.

    Call this tool to fetch all object schemas from the HubSpot CRM, which can help understand data structure within the CRM."""  # 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/crm-object-schemas/v3/schemas",
        method="GET",
        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=["crm.schemas.custom.write"]))
async def create_crm_object_schema(
    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-/crm-object-schemas/v3/schemas_create'."
]:
    """Create a new CRM object schema in HubSpot.

    Use this tool to create a new object schema in HubSpot's CRM. This is useful when you need to define a new structure for CRM objects tailored to specific business needs.

    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["CREATECRMOBJECTSCHEMA"],
            "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["CREATECRMOBJECTSCHEMA"] + "\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["CREATECRMOBJECTSCHEMA"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm-object-schemas/v3/schemas",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECRMOBJECTSCHEMA"],
        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=[
            "crm.objects.custom.highly_sensitive.read.v2",
            "crm.objects.custom.sensitive.read.v2",
            "crm.schemas.custom.read",
            "crm.objects.custom.read",
        ],
    )
)
async def get_crm_object_schema(
    context: ToolContext,
    object_type: Annotated[
        str, "The fully qualified name or object type ID of the CRM schema to retrieve."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm-object-schemas/v3/schemas/{objectType}_getById'.",
]:
    """Retrieve a CRM object schema by its type.

    Use this tool to get detailed information about a specific CRM object schema by providing the object type. This helps in understanding the structure and properties of the CRM object within HubSpot."""  # 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/crm-object-schemas/v3/schemas/{objectType}".format(  # noqa: UP032
            objectType=object_type
        ),
        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=["crm.schemas.custom.write"]))
async def delete_crm_object_schema(
    context: ToolContext,
    object_type_identifier: Annotated[
        str, "The fully qualified name or object type ID of the schema to delete."
    ],
    return_only_archived_results: Annotated[
        bool | None, "Set to True to return only archived results."
    ] = False,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm-object-schemas/v3/schemas/{objectType}_archive'.",
]:
    """Delete a CRM object schema in HubSpot.

    This tool deletes a specified CRM object schema in HubSpot. Use it when you need to remove an existing schema, ensuring it's no longer accessible or in 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/crm-object-schemas/v3/schemas/{objectType}".format(  # noqa: UP032
            objectType=object_type_identifier
        ),
        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=["crm.schemas.custom.write"]))
async def update_crm_object_schema(
    context: ToolContext,
    object_type_identifier: Annotated[
        str, "Fully qualified name or object type ID of your CRM schema for updates."
    ],
    clear_description: Annotated[
        bool | None, "Set to true to clear the description field for the object type schema."
    ] = None,
    object_description: Annotated[
        str | None,
        "A description for the CRM object schema, providing details about its purpose or usage in HubSpot.",  # noqa: E501
    ] = None,
    object_singular_name: Annotated[
        str | None, "The word representing a single object. This cannot be changed later."
    ] = None,
    plural_labels: Annotated[
        str | None,
        "Specify the word representing multiple instances of the object type. This value is permanent and cannot be changed after setting.",  # noqa: E501
    ] = None,
    primary_display_property: Annotated[
        str | None,
        "The primary property's name for this object, displayed prominently on the HubSpot record page.",  # noqa: E501
    ] = None,
    required_properties: Annotated[
        list[str] | None,
        "List of property names that must be provided when creating an object of this type in HubSpot.",  # noqa: E501
    ] = None,
    restorable: Annotated[
        bool | None,
        "Indicates if the object can be restored after deletion. Accepts a boolean value.",
    ] = None,
    searchable_properties: Annotated[
        list[str] | None,
        "List of property names to be indexed for HubSpot's product search, enhancing searchability of the CRM object type.",  # noqa: E501
    ] = None,
    secondary_display_properties: Annotated[
        list[str] | None,
        "Names of secondary properties displayed on the HubSpot record page for this object type.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'patch-/crm-object-schemas/v3/schemas/{objectType}_update'.",
]:
    """Update a CRM object's schema in HubSpot.

    This tool updates the schema of a specified CRM object type in HubSpot. Call this tool when you need to modify the structure or attributes of an existing CRM object schema."""  # noqa: E501
    request_data: Any = {
        "secondaryDisplayProperties": secondary_display_properties,
        "requiredProperties": required_properties,
        "searchableProperties": searchable_properties,
        "clearDescription": clear_description,
        "primaryDisplayProperty": primary_display_property,
        "description": object_description,
        "restorable": restorable,
        "labels": {"plural": plural_labels, "singular": object_singular_name},
    }
    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/crm-object-schemas/v3/schemas/{objectType}".format(  # noqa: UP032
            objectType=object_type_identifier
        ),
        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=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_auth=OAuth2(id="arcade-hubspot", scopes=["crm.schemas.custom.write"]))
async def create_crm_object_association(
    context: ToolContext,
    crm_object_type_schema: Annotated[
        str,
        "Fully qualified name or object type ID of your CRM object schema to create the association.",  # noqa: E501
    ],
    primary_object_type_id: Annotated[
        str, "ID of the primary object type to link from in the CRM system."
    ],
    target_object_type_id: Annotated[
        str, "ID of the target object type to link to in the CRM association."
    ],
    association_name: Annotated[
        str | None,
        "A unique name for the association between CRM objects. This helps identify the link.",
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'post-/crm-object-schemas/v3/schemas/{objectType}/associations_createAssociation'.",  # noqa: E501
]:
    """Create an association between HubSpot CRM objects.

    This tool creates an association between specified HubSpot CRM objects. Use it when you need to link different CRM objects (like contacts and companies) within the HubSpot platform."""  # noqa: E501
    request_data: Any = {
        "fromObjectTypeId": primary_object_type_id,
        "name": association_name,
        "toObjectTypeId": target_object_type_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/crm-object-schemas/v3/schemas/{objectType}/associations".format(  # noqa: UP032
            objectType=crm_object_type_schema
        ),
        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=["crm.schemas.custom.write"]))
async def delete_crm_association(
    context: ToolContext,
    association_id: Annotated[str, "Unique ID of the association to be removed."],
    schema_object_type: Annotated[
        str,
        "The fully qualified name or object type ID of your schema to identify which CRM object to target.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm-object-schemas/v3/schemas/{objectType}/associations/{associationIdentifier}_archiveAssociation'.",  # noqa: E501
]:
    """Remove an association between CRM object schemas.

    This tool is used to delete an association between specified CRM object schemas in HubSpot. It should be called when there is a need to remove or archive a link between two object types."""  # 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/crm-object-schemas/v3/schemas/{objectType}/associations/{associationIdentifier}".format(  # noqa: UP032
            objectType=schema_object_type, associationIdentifier=association_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=["crm.objects.services.read"]))
async def fetch_hubspot_object_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Indicate if only archived records should be returned when retrieving HubSpot CRM data.  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-/crm/v3/objects/0-162/batch/read'."
]:
    """Retrieve HubSpot CRM records by ID or custom property.

    Use this tool to retrieve HubSpot CRM records by specifying record IDs or by using a custom unique value property with the `idProperty` parameter.

    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["FETCHHUBSPOTOBJECTRECORDS"],
            "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["FETCHHUBSPOTOBJECTRECORDS"] + "\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["FETCHHUBSPOTOBJECTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["FETCHHUBSPOTOBJECTRECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.services.write"]))
async def update_services_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-/crm/v3/objects/0-162/batch/update'."
]:
    """Update multiple service records in HubSpot CRM.

    Use this tool to update a batch of service records in the HubSpot CRM by their internal IDs or unique property values. Suitable for synchronizing or modifying multiple records efficiently.

    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["UPDATESERVICESBATCH"],
            "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["UPDATESERVICESBATCH"] + "\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["UPDATESERVICESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATESERVICESBATCH"],
        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=["crm.objects.services.write"]))
async def hubspot_crm_upsert_records(
    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-/crm/v3/objects/0-162/batch/upsert'."
]:
    """Create or update unique records in HubSpot CRM.

    This tool allows for the creation or update of CRM records in HubSpot, identified by a unique property. Use it when you need to batch upload or modify records based on a unique identifier.

    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["HUBSPOTCRMUPSERTRECORDS"],
            "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["HUBSPOTCRMUPSERTRECORDS"] + "\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["HUBSPOTCRMUPSERTRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["HUBSPOTCRMUPSERTRECORDS"],
        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=["crm.objects.services.read"]))
async def hubspot_search_custom_objects(
    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-/crm/v3/objects/0-162/search'."
]:
    """Search for custom objects in HubSpot CRM.

    Use this tool to search for custom objects within the HubSpot CRM. It should be called when you need to retrieve specific custom object data based on certain criteria.

    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["HUBSPOTSEARCHCUSTOMOBJECTS"],
            "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["HUBSPOTSEARCHCUSTOMOBJECTS"]
                + "\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["HUBSPOTSEARCHCUSTOMOBJECTS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["HUBSPOTSEARCHCUSTOMOBJECTS"],
        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=["crm.objects.services.write"]))
async def archive_services_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-/crm/v3/objects/0-162/batch/archive'."
]:
    """Archive multiple services using their IDs in bulk.

    Use this tool to archive a batch of services in the CRM by providing their IDs. This is useful for managing large sets of records efficiently.

    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["ARCHIVESERVICESBATCH"],
            "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["ARCHIVESERVICESBATCH"] + "\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["ARCHIVESERVICESBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVESERVICESBATCH"],
        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=["crm.objects.services.read"]))
async def get_hubspot_object_by_id(
    context: ToolContext,
    hubspot_crm_service_id: Annotated[
        str,
        "The unique identifier (service ID) of the HubSpot CRM object to retrieve. This can be the internal object ID or any unique property defined by the `idProperty`.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types whose associated IDs should be retrieved. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return along with their history for a HubSpot CRM object. Properties should be specified as strings in the list.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived results. False returns non-archived results.",
    ] = False,
    return_properties_list: Annotated[
        list[str] | None,
        "A list of properties to be returned in the response. Non-existing properties will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None, "Specify the name of a property with unique values for the object."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-162/{serviceId}'."
]:
    """Retrieve a HubSpot CRM object using its service ID.

    This tool is used to read a specific HubSpot CRM object identified by its `serviceId`. It can optionally use any unique property value by specifying the `idProperty`. The returned data can be customized with the `properties` 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/crm/v3/objects/0-162/{serviceId}".format(  # noqa: UP032
            serviceId=hubspot_crm_service_id
        ),
        method="GET",
        params=remove_none_values({
            "properties": return_properties_list,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_archived_only,
            "idProperty": unique_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=["crm.objects.services.write"]))
async def delete_object_hubspot(
    context: ToolContext,
    object_service_id: Annotated[
        str, "The unique identifier for the object to be moved to the recycling bin in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/0-162/{serviceId}'."
]:
    """Move an object to the recycling bin in HubSpot CRM.

    Use this tool to delete an object identified by `serviceId` within the HubSpot CRM, moving it to the recycling bin."""  # 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/crm/v3/objects/0-162/{serviceId}".format(  # noqa: UP032
            serviceId=object_service_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=["crm.objects.services.write"]))
async def edit_hubspot_object(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    object_identifier: Annotated[
        str | None,
        "The unique identifier or `{serviceId}` for the object to be updated. This can be an internal object ID or a unique property value when used with `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a unique property for the object, used instead of the internal ID if specified.  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-/crm/v3/objects/0-162/{serviceId}'."
]:
    """Partially update a HubSpot CRM object with specified properties.

    This tool performs a partial update on a HubSpot CRM object identified by the `serviceId` or a unique property value using the `idProperty` query parameter. It overwrites provided properties while ensuring read-only or non-existent properties result in errors. Properties can be cleared with an empty string.

    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["EDITHUBSPOTOBJECT"],
            "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 object_identifier:
        missing_params.append(("object_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["EDITHUBSPOTOBJECT"] + "\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["EDITHUBSPOTOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/{serviceId}".format(  # noqa: UP032
            serviceId=object_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["EDITHUBSPOTOBJECT"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.services.read"]))
async def read_services_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for each service. Ignored if associations do not exist.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None, "The cursor token from the last read to retrieve the next page of results."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of properties to return with their history of values. If absent, properties are ignored. Reduces max services per request.",  # noqa: E501
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to include in the response. Non-existent properties will be ignored.",  # noqa: E501
    ] = None,
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived results; false to include non-archived results.",
    ] = False,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/0-162'."]:
    """Retrieve a page of services with customizable properties.

    This tool retrieves a page of services from the HubSpot CRM API, allowing customization of returned data via the `properties` query parameter. Use this tool to access service information efficiently."""  # 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/crm/v3/objects/0-162",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "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=["crm.objects.services.write"]))
async def create_service_record(
    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-/crm/v3/objects/0-162'."]:
    """Create a service record in HubSpot CRM.

    This tool creates a new service object in HubSpot CRM with the specified properties and returns the created object, including its ID. It's useful for adding new service records to the CRM 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["CREATESERVICERECORD"],
            "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["CREATESERVICERECORD"] + "\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["CREATESERVICERECORD"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATESERVICERECORD"],
        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=["crm.objects.services.write"]))
async def create_batch_of_services(
    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-/crm/v3/objects/0-162/batch/create'."
]:
    """Create a batch of services in HubSpot CRM.

    Use this tool to create multiple service entries at once within HubSpot CRM. This is useful for efficiently managing and organizing service-related data.

    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["CREATEBATCHOFSERVICES"],
            "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["CREATEBATCHOFSERVICES"] + "\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["CREATEBATCHOFSERVICES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/0-162/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHOFSERVICES"],
        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=["crm.objects.contacts.write"]))
async def archive_hubspot_tasks(
    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-/crm/v3/objects/tasks/batch/archive_archive'.",
]:
    """Archive multiple HubSpot tasks by their IDs.

    Use this tool to archive a batch of tasks in HubSpot CRM by specifying their IDs. It should be called when you need to update the status of tasks to archived.

    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["ARCHIVEHUBSPOTTASKS"],
            "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["ARCHIVEHUBSPOTTASKS"] + "\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["ARCHIVEHUBSPOTTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEHUBSPOTTASKS"],
        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=["crm.objects.contacts.read"]))
async def get_task_details(
    context: ToolContext,
    task_id: Annotated[
        str,
        "The unique identifier for the HubSpot CRM task. Retrieve specific task details using this ID.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types to retrieve associated IDs for. Returns IDs of related objects.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their previous values. Comma-separated and ignored if not present.",  # noqa: E501
    ] = None,
    requested_properties: Annotated[
        list[str] | None,
        "List of properties to return for the task. Only available properties will be returned.",
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
    unique_property_name: Annotated[
        str | None, "Specify the name of the property with unique values to identify the task."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/tasks/{taskId}_getById'."
]:
    """Retrieve HubSpot CRM task details using task ID.

    Call this tool to get detailed information about a specific task in HubSpot CRM by providing the task ID. Modify the returned data using query parameters 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/crm/v3/objects/tasks/{taskId}".format(taskId=task_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": requested_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_archived,
            "idProperty": unique_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=["crm.objects.contacts.write"]))
async def delete_task_in_hubspot(
    context: ToolContext,
    task_id: Annotated[
        str,
        "The unique identifier of the task to be deleted from HubSpot CRM. Required to move the task to the recycling bin.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/tasks/{taskId}_archive'.",
]:
    """Delete a task in HubSpot by task ID.

    Use this tool to move a specific task in HubSpot CRM to the recycling bin by providing the task 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/crm/v3/objects/tasks/{taskId}".format(taskId=task_id),  # noqa: UP032
        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=["crm.objects.contacts.write"]))
async def update_hubspot_task(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    task_identifier: Annotated[
        str | None,
        "The internal ID or unique property name of the task to update. Defaults to internal ID.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specify the name of the property with unique values. Used for identifying the object instead of `taskId`.  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-/crm/v3/objects/tasks/{taskId}_update'."
]:
    """Update properties of a HubSpot task using its ID.

    This tool allows partial updates to a task in HubSpot CRM by specifying a `{taskId}` or a unique property defined by `idProperty`. It overwrites specified properties, with read-only and non-existent properties triggering errors. To clear a property, pass an empty string.

    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["UPDATEHUBSPOTTASK"],
            "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 task_identifier:
        missing_params.append(("task_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["UPDATEHUBSPOTTASK"] + "\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["UPDATEHUBSPOTTASK"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/{taskId}".format(taskId=task_identifier),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTTASK"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.contacts.write"]))
async def create_batch_tasks(
    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-/crm/v3/objects/tasks/batch/create_create'.",
]:
    """Create a batch of tasks in HubSpot CRM.

    This tool is used to create multiple tasks at once in HubSpot CRM, streamlining task management 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["CREATEBATCHTASKS"],
            "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["CREATEBATCHTASKS"] + "\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["CREATEBATCHTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHTASKS"],
        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=["crm.objects.contacts.write"]))
async def update_batch_tasks(
    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-/crm/v3/objects/tasks/batch/update_update'.",
]:
    """Update a batch of tasks in HubSpot CRM.

    Use this tool to update multiple tasks in HubSpot CRM by their internal ID or unique property values. It should be called when you need to modify several tasks at once, such as changing task details or status across multiple entries.

    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["UPDATEBATCHTASKS"],
            "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["UPDATEBATCHTASKS"] + "\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["UPDATEBATCHTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHTASKS"],
        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=["crm.objects.contacts.read"]))
async def retrieve_hubspot_tasks(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived records from HubSpot.  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-/crm/v3/objects/tasks/batch/read_read'."
]:
    """Retrieve HubSpot task records by ID or custom property.

    Use this tool to fetch task records from HubSpot CRM by specifying record IDs or a custom unique value property via the `idProperty` parameter.

    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["RETRIEVEHUBSPOTTASKS"],
            "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["RETRIEVEHUBSPOTTASKS"] + "\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["RETRIEVEHUBSPOTTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTTASKS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.contacts.write"]))
async def upsert_hubspot_tasks(
    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-/crm/v3/objects/tasks/batch/upsert_upsert'.",
]:
    """Create or update tasks in HubSpot using a unique property.

    This tool creates or updates HubSpot CRM tasks identified by a unique property value specified by the `idProperty` query parameter. Use this tool when you need to batch upsert tasks based on unique identifiers.

    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["UPSERTHUBSPOTTASKS"],
            "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["UPSERTHUBSPOTTASKS"] + "\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["UPSERTHUBSPOTTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTTASKS"],
        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=["crm.objects.contacts.read"]))
async def search_hubspot_tasks(
    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-/crm/v3/objects/tasks/search_doSearch'."
]:
    """Search for tasks in HubSpot CRM.

    Use this tool to search for tasks within HubSpot CRM. It should be called when you need to find specific tasks based on certain search criteria.

    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["SEARCHHUBSPOTTASKS"],
            "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["SEARCHHUBSPOTTASKS"] + "\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["SEARCHHUBSPOTTASKS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHHUBSPOTTASKS"],
        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=["crm.objects.contacts.read"]))
async def get_tasks_list(
    context: ToolContext,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of task results to retrieve per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token indicating the last resource read, used for pagination to continue from the next page.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to return along with their full change history. Note: This reduces the number of tasks per request.",  # noqa: E501
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. If specified associations do not exist, they will be ignored.",  # noqa: E501
    ] = None,
    return_only_archived_results: Annotated[
        bool | None, "Set to true to return only archived tasks; false for active tasks."
    ] = False,
    task_properties_to_return: Annotated[
        list[str] | None,
        "A list of property names to include in the response. Ignored if not present on the tasks.",
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/tasks_getPage'."
]:
    """Retrieve a page of tasks from HubSpot CRM.

    Use this tool to read a page of tasks from HubSpot CRM. Specify which properties to return using the 'properties' 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/crm/v3/objects/tasks",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": task_properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "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=["crm.objects.contacts.write"]))
async def create_task_in_crm(
    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-/crm/v3/objects/tasks_create'."
]:
    """Create a task in HubSpot CRM and return task details.

    This tool creates a task in HubSpot CRM with specified properties and returns the task details, including the ID. It should be called when you need to add a new task to the CRM 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["CREATETASKINCRM"],
            "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["CREATETASKINCRM"] + "\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["CREATETASKINCRM"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tasks",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATETASKINCRM"],
        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=["crm.objects.line_items.read"]))
async def get_taxes_page(
    context: ToolContext,
    archived_only: Annotated[
        bool | None, "Set to true to return only archived tax results."
    ] = False,
    included_properties: Annotated[
        list[str] | None,
        "List the properties to be returned for each tax. Specify as an array of strings.",
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of tax results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The token for the paging cursor from the last read resource for fetching the next page of results.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "Specify properties to return with their history. Reduces max number of taxes per request.",
    ] = None,
    retrieve_associated_object_ids: Annotated[
        list[str] | None,
        "Comma-separated list of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/taxes_getPage'."
]:
    """Retrieve a page of tax details from HubSpot CRM.

    Use this tool to read a specific page of tax details from the HubSpot CRM. You can control which properties are returned by specifying them in the query parameters."""  # 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/crm/v3/objects/taxes",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": included_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": retrieve_associated_object_ids,
            "archived": 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=["crm.objects.line_items.write"]))
async def create_hubspot_tax(
    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-/crm/v3/objects/taxes_create'."
]:
    """Create a tax in HubSpot CRM and retrieve its details.

    This tool creates a tax with specified properties in HubSpot CRM and returns a detailed object including the newly assigned ID. Use this for adding new tax records to your CRM 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["CREATEHUBSPOTTAX"],
            "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["CREATEHUBSPOTTAX"] + "\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["CREATEHUBSPOTTAX"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEHUBSPOTTAX"],
        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=["crm.objects.line_items.read"]))
async def get_tax_details_by_id(
    context: ToolContext,
    tax_id: Annotated[
        str,
        "The unique ID or property value for the tax object. Default is the internal object ID.",
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types to retrieve associated IDs for. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List properties to return with their historical values. Use a comma-separated format.",
    ] = None,
    return_archived_only: Annotated[
        bool | None,
        "Set to true to return only archived results. False returns non-archived records.",
    ] = False,
    return_properties: Annotated[
        list[str] | None,
        "Comma-separated list of properties to be included in the response. Non-existing properties will be ignored.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property whose values are unique for the tax object. Use this to specify an alternative ID property instead of the default internal ID.",  # noqa: E501
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/taxes/{taxId}_getById'."
]:
    """Retrieve tax details using a specific tax ID.

    This tool retrieves tax object details from HubSpot CRM by specifying a tax ID. The ID can be the internal object ID or any unique property value as set by the `idProperty` query parameter. Useful for accessing specific tax information within HubSpot."""  # 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/crm/v3/objects/taxes/{taxId}".format(taxId=tax_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": return_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_archived_only,
            "idProperty": unique_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=["crm.objects.line_items.write"]))
async def delete_tax_entry(
    context: ToolContext,
    tax_entry_id: Annotated[
        str, "The unique identifier for the tax entry you want to archive in HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/taxes/{taxId}_archive'."
]:
    """Archive a tax entry in HubSpot CRM.

    Use this tool to move a tax object identified by `taxId` to the recycling bin in HubSpot CRM."""
    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/crm/v3/objects/taxes/{taxId}".format(taxId=tax_entry_id),  # noqa: UP032
        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=["crm.objects.line_items.write"]))
async def update_tax_object(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    tax_object_identifier: Annotated[
        str | None,
        "The identifier for the tax object. Use the internal `taxId` by default or provide a unique property value specified by the `idProperty`.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property uniquely identifying this tax object. Used instead of `taxId` if specified.  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-/crm/v3/objects/taxes/{taxId}_update'."
]:
    """Update properties of a tax object in HubSpot CRM.

    This tool updates specified properties of a tax object in HubSpot CRM using its ID or a unique property value. It's used to perform partial updates where only certain fields need modification. Fields to be updated are specified in the request, and read-only or non-existent fields will cause errors. To clear a property, pass an empty string.

    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["UPDATETAXOBJECT"],
            "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 tax_object_identifier:
        missing_params.append(("tax_object_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["UPDATETAXOBJECT"] + "\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["UPDATETAXOBJECT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/{taxId}".format(  # noqa: UP032
            taxId=tax_object_identifier
        ),
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATETAXOBJECT"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.line_items.write"]))
async def create_or_update_tax_records(
    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-/crm/v3/objects/taxes/batch/upsert_upsert'.",
]:
    """Create or update tax records based on unique properties.

    This tool creates or updates tax records in HubSpot CRM by using unique property values specified by the `idProperty` parameter. It should be called when you need to ensure tax records are correctly up-to-date by leveraging existing unique identifiers.

    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["CREATEORUPDATETAXRECORDS"],
            "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["CREATEORUPDATETAXRECORDS"] + "\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["CREATEORUPDATETAXRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEORUPDATETAXRECORDS"],
        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=["crm.objects.line_items.write"]))
async def update_batch_taxes(
    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-/crm/v3/objects/taxes/batch/update_update'.",
]:
    """Update taxes in batch using IDs or unique values.

    Call this tool to update multiple tax records at once in HubSpot CRM using either internal IDs or unique property values.

    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["UPDATEBATCHTAXES"],
            "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["UPDATEBATCHTAXES"] + "\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["UPDATEBATCHTAXES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEBATCHTAXES"],
        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=["crm.objects.line_items.read"]))
async def retrieve_tax_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_only: Annotated[
        bool | None,
        "Set to true to retrieve only archived records, false to exclude them.  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-/crm/v3/objects/taxes/batch/read_read'."
]:
    """Retrieve tax records by ID or custom property.

    Use this tool to retrieve tax records by specifying record IDs or using a custom unique value property. It's useful when you need detailed tax information for specific records.

    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["RETRIEVETAXRECORDS"],
            "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["RETRIEVETAXRECORDS"] + "\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["RETRIEVETAXRECORDS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVETAXRECORDS"],
        params=remove_none_values({"archived": return_archived_only}),
        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=["crm.objects.line_items.write"]))
async def create_tax_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-/crm/v3/objects/taxes/batch/create_create'.",
]:
    """Create a batch of taxes in HubSpot CRM.

    Use this tool to create multiple tax records at once in HubSpot CRM. Ideal for situations where bulk tax data entry is required.

    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["CREATETAXBATCH"],
            "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["CREATETAXBATCH"] + "\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["CREATETAXBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATETAXBATCH"],
        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=["crm.objects.line_items.read"]))
async def search_taxes(
    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-/crm/v3/objects/taxes/search_doSearch'."
]:
    """Search for tax entries within HubSpot CRM.

    Use this tool to find specific tax records in HubSpot CRM based on search criteria. It returns relevant tax entries from the CRM database.

    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["SEARCHTAXES"],
            "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["SEARCHTAXES"] + "\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["SEARCHTAXES"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHTAXES"],
        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=["crm.objects.line_items.write"]))
async def archive_tax_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-/crm/v3/objects/taxes/batch/archive_archive'.",
]:
    """Archive a batch of taxes by their IDs.

    This tool archives a batch of taxes in the HubSpot CRM by their IDs. It should be called when you need to remove or deactivate multiple tax records 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["ARCHIVETAXBATCH"],
            "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["ARCHIVETAXBATCH"] + "\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["ARCHIVETAXBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/taxes/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVETAXBATCH"],
        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=["tickets"]))
async def merge_support_tickets(
    context: ToolContext,
    primary_ticket_id: Annotated[
        str,
        "The ID of the ticket designated as the primary record in the merge operation. After merging, this ticket will contain all combined information.",  # noqa: E501
    ],
    secondary_ticket_id: Annotated[
        str,
        "The ID of the support ticket to be merged into the primary ticket. It specifies which ticket will be combined with the primary ticket record.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'post-/crm/v3/objects/tickets/merge_merge'."
]:
    """Merge two support tickets into one unified record.

    This tool merges two separate support tickets into a single ticket record, combining all the relevant information. It should be used when consolidating duplicate or related tickets is necessary to streamline customer support operations."""  # noqa: E501
    request_data: Any = {
        "objectIdToMerge": secondary_ticket_id,
        "primaryObjectId": primary_ticket_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/crm/v3/objects/tickets/merge",
        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=["tickets"]))
async def delete_hubspot_tickets_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-/crm/v3/objects/tickets/batch/archive_archive'.",
]:
    """Delete a batch of tickets in HubSpot CRM.

    Use this tool to delete multiple tickets by ID in HubSpot CRM. Deleted tickets can be restored within 90 days.

    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["DELETEHUBSPOTTICKETSBATCH"],
            "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["DELETEHUBSPOTTICKETSBATCH"] + "\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["DELETEHUBSPOTTICKETSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["DELETEHUBSPOTTICKETSBATCH"],
        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=["tickets"]))
async def get_ticket_details(
    context: ToolContext,
    ticket_id: Annotated[
        str,
        "The ID of the ticket to retrieve from HubSpot CRM. This can be the internal object ID or a unique property value based on the `idProperty`.",  # noqa: E501
    ],
    associated_object_types: Annotated[
        list[str] | None,
        "List of object types for retrieving associated IDs. Non-existent associations will be ignored.",  # noqa: E501
    ] = None,
    only_return_archived: Annotated[
        bool | None, "Set to true to return only archived results."
    ] = False,
    properties_to_return: Annotated[
        list[str] | None,
        "A list of properties to include in the response. Properties not present will be ignored.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties with their history to be returned. If properties are missing, they'll be ignored.",  # noqa: E501
    ] = None,
    unique_identifier_property: Annotated[
        str | None, "Specifies the property name with unique values for identifying the ticket."
    ] = None,
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/objects/tickets/{ticketId}_getById'.",
]:
    """Retrieve details of a ticket by ID from HubSpot CRM.

    This tool retrieves detailed information about a specific ticket in HubSpot CRM identified by `ticketId`. It's useful for obtaining comprehensive ticket 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/crm/v3/objects/tickets/{ticketId}".format(ticketId=ticket_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": only_return_archived,
            "idProperty": unique_identifier_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=["tickets"]))
async def delete_ticket(
    context: ToolContext,
    ticket_id: Annotated[
        str,
        "The unique ID of the ticket to move to the recycling bin. Must be a valid string representing the ticket identifier.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/objects/tickets/{ticketId}_archive'.",
]:
    """Move a ticket to the recycling bin by ticket ID.

    Use this tool to delete a CRM ticket by moving it to the recycling bin using its unique ticket 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/crm/v3/objects/tickets/{ticketId}".format(ticketId=ticket_id),  # noqa: UP032
        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=["tickets"]))
async def update_ticket_info(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    ticket_id: Annotated[
        str | None,
        "The internal ID of the ticket to be updated. This is used to identify the specific ticket in HubSpot CRM.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "The name of a property whose values are unique for the ticket object. Specify if not using the default ticket ID.  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-/crm/v3/objects/tickets/{ticketId}_update'.",
]:
    """Partially update ticket details in HubSpot CRM.

    Use this tool to perform a partial update on a ticket identified by its `{ticketId}` or a unique property value specified by `idProperty` in HubSpot CRM. Read-only and non-existent properties will cause errors. Clear property values by passing an empty string.

    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["UPDATETICKETINFO"],
            "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 ticket_id:
        missing_params.append(("ticket_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["UPDATETICKETINFO"] + "\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["UPDATETICKETINFO"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/{ticketId}".format(ticketId=ticket_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATETICKETINFO"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["tickets"]))
async def upsert_crm_tickets(
    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-/crm/v3/objects/tickets/batch/upsert_upsert'.",
]:
    """Create or update CRM tickets in bulk using unique identifiers.

    This tool allows you to create or update ticket records in HubSpot CRM by using a unique identifier specified through the `idProperty` query parameter. This is useful for ensuring ticket data is up-to-date or for adding new ticket entries in bulk.

    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["UPSERTCRMTICKETS"],
            "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["UPSERTCRMTICKETS"] + "\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["UPSERTCRMTICKETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTCRMTICKETS"],
        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"))
async def retrieve_ticket_batch(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_archived_tickets_only: Annotated[
        bool | None,
        "Set to true to return only archived tickets. If false, include non-archived tickets as well.  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-/crm/v3/objects/tickets/batch/read_read'."
]:
    """Retrieve a batch of tickets by ID or property value.

    Use this tool to fetch a batch of CRM tickets by providing their IDs or a specific unique property value. Ideal for obtaining detailed information about multiple tickets simultaneously.

    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["RETRIEVETICKETBATCH"],
            "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["RETRIEVETICKETBATCH"] + "\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["RETRIEVETICKETBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVETICKETBATCH"],
        params=remove_none_values({"archived": return_archived_tickets_only}),
        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=["tickets"]))
async def fetch_tickets_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types (e.g. 'contacts', 'companies') to retrieve associated IDs for tickets. Ignore if they don't exist.",  # noqa: E501
    ] = None,
    include_properties: Annotated[
        list[str] | None,
        "A list of properties to return for each ticket. Specify as strings; unlisted properties will be ignored.",  # noqa: E501
    ] = None,
    paging_cursor_token: Annotated[
        str | None, "The token for the last read resource to continue paging results."
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "A list of property names to return with their historical values. Reduces the number of tickets retrievable per request.",  # noqa: E501
    ] = None,
    results_per_page: Annotated[
        int | None, "The maximum number of results to display per page. Must be a positive integer."
    ] = 10,
    return_only_archived: Annotated[
        bool | None, "Return only archived tickets if set to 'True'."
    ] = False,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/tickets_getPage'."
]:
    """Retrieve a page of tickets from HubSpot CRM.

    Use this tool to read a page of tickets from HubSpot CRM. You can control the returned data using the `properties` 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/crm/v3/objects/tickets",
        method="GET",
        params=remove_none_values({
            "limit": results_per_page,
            "after": paging_cursor_token,
            "properties": include_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["tickets"]))
async def create_ticket(
    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-/crm/v3/objects/tickets_create'."
]:
    """Create a support ticket in HubSpot CRM.

    Use this tool to create a new support ticket with specified properties in HubSpot CRM and receive a copy of the ticket object along with its ID.

    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["CREATETICKET"],
            "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["CREATETICKET"] + "\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["CREATETICKET"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATETICKET"],
        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=["tickets"]))
async def search_tickets(
    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-/crm/v3/objects/tickets/search_doSearch'."
]:
    """Search and filter CRM tickets based on properties and associations.

    Utilize this tool to search for tickets within the CRM by applying specific filters on properties, examining associations, and sorting the results. Ideal for retrieving detailed ticket information based on particular criteria.

    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["SEARCHTICKETS"],
            "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["SEARCHTICKETS"] + "\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["SEARCHTICKETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHTICKETS"],
        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=["tickets"]))
async def create_batch_tickets(
    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-/crm/v3/objects/tickets/batch/create_create'.",
]:
    """Create a batch of tickets in HubSpot CRM.

    Use this tool to create multiple tickets in HubSpot CRM at once. You can specify property values for each ticket and define associations with other CRM records.

    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["CREATEBATCHTICKETS"],
            "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["CREATEBATCHTICKETS"] + "\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["CREATEBATCHTICKETS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEBATCHTICKETS"],
        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=["tickets"]))
async def update_ticket_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-/crm/v3/objects/tickets/batch/update_update'.",
]:
    """Update multiple tickets in HubSpot CRM by ID or property.

    This tool updates a batch of tickets in the HubSpot CRM using their IDs or a unique property value. Use it when you need to modify several tickets at once. Properties that are read-only or non-existent will cause an error, and providing an empty string will clear a property value.

    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["UPDATETICKETBATCH"],
            "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["UPDATETICKETBATCH"] + "\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["UPDATETICKETBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/tickets/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATETICKETBATCH"],
        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=[
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.schemas.companies.read",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.contacts.read",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.schemas.deals.read",
            "crm.objects.deals.read",
            "tickets",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.objects.companies.read",
            "timeline",
        ],
    )
)
async def retrieve_event_details(
    context: ToolContext,
    event_id: Annotated[
        str,
        "Specify the event ID to retrieve detailed information for a specific event in HubSpot CRM.",  # noqa: E501
    ],
    event_template_id: Annotated[
        str,
        "The ID of the event template used to identify and retrieve specific event details in HubSpot CRM.",  # noqa: E501
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/integrators/timeline/v3/events/{eventTemplateId}/{eventId}/detail_getDetailById'.",  # noqa: E501
]:
    """Retrieve detailed information for a specific HubSpot event.

    Use this tool to get detailed information about a specific event in HubSpot CRM, identified by its template ID and event 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/integrators/timeline/v3/events/{eventTemplateId}/{eventId}/detail".format(  # noqa: UP032
            eventTemplateId=event_template_id, eventId=event_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=[
            "crm.schemas.deals.write",
            "crm.objects.contacts.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "tickets.sensitive.v2",
            "tickets.highly_sensitive.v2",
            "crm.schemas.companies.write",
            "crm.objects.companies.write",
            "tickets",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.schemas.contacts.write",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def send_event_to_hubspot(
    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-/integrators/timeline/v3/events_create'."
]:
    """Send event data to a specified HubSpot event type.

    This tool is used to send a single instance of event data to a specified event type in HubSpot CRM. It should be called when there's a need to submit event data to HubSpot for tracking or analysis.

    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["SENDEVENTTOHUBSPOT"],
            "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["SENDEVENTTOHUBSPOT"] + "\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["SENDEVENTTOHUBSPOT"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/integrators/timeline/v3/events",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SENDEVENTTOHUBSPOT"],
        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=[
            "crm.objects.companies.highly_sensitive.read.v2",
            "crm.schemas.companies.read",
            "tickets.sensitive.v2",
            "crm.schemas.contacts.read",
            "tickets.highly_sensitive.v2",
            "crm.objects.contacts.read",
            "crm.objects.deals.highly_sensitive.read.v2",
            "crm.schemas.deals.read",
            "crm.objects.deals.read",
            "tickets",
            "crm.objects.companies.sensitive.read.v2",
            "crm.objects.deals.sensitive.read.v2",
            "crm.objects.contacts.highly_sensitive.read.v2",
            "crm.objects.contacts.sensitive.read.v2",
            "crm.objects.companies.read",
            "timeline",
        ],
    )
)
async def retrieve_event_instance(
    context: ToolContext,
    event_id: Annotated[str, "The unique identifier for the specific event you want to retrieve."],
    event_template_id: Annotated[
        str, "The unique ID of the event template required to retrieve the event instance."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/integrators/timeline/v3/events/{eventTemplateId}/{eventId}_getById'.",  # noqa: E501
]:
    """Retrieve an event instance using template and event ID.

    Use this tool to access detailed information about a specific event instance by providing the event template ID and event 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/integrators/timeline/v3/events/{eventTemplateId}/{eventId}".format(  # noqa: UP032
            eventTemplateId=event_template_id, eventId=event_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=[
            "crm.schemas.deals.write",
            "crm.objects.contacts.write",
            "crm.objects.companies.highly_sensitive.write.v2",
            "tickets.sensitive.v2",
            "tickets.highly_sensitive.v2",
            "crm.schemas.companies.write",
            "crm.objects.companies.write",
            "tickets",
            "crm.objects.deals.highly_sensitive.write.v2",
            "crm.objects.deals.write",
            "crm.schemas.contacts.write",
            "crm.objects.contacts.sensitive.write.v2",
            "crm.objects.contacts.highly_sensitive.write.v2",
            "timeline",
            "crm.objects.companies.sensitive.write.v2",
            "crm.objects.deals.sensitive.write.v2",
        ],
    )
)
async def batch_create_timeline_events(
    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-/integrators/timeline/v3/events/batch/create_createBatch'.",  # noqa: E501
]:
    """Batch create multiple timeline event instances.

    Batch create multiple timeline events using an event template. Created events are immutable and can trigger updates to object properties if configured in the template.

    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["BATCHCREATETIMELINEEVENTS"],
            "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["BATCHCREATETIMELINEEVENTS"] + "\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["BATCHCREATETIMELINEEVENTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/integrators/timeline/v3/events/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["BATCHCREATETIMELINEEVENTS"],
        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=["crm.extensions_calling_transcripts.write"])
)
async def upload_call_transcripts(
    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-/crm/v3/extensions/calling/transcripts'."
]:
    """Upload call transcripts to HubSpot CRM.

    Use this tool to upload call transcripts to HubSpot CRM. It should be called when you need to store transcripts for CRM 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["UPLOADCALLTRANSCRIPTS"],
            "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["UPLOADCALLTRANSCRIPTS"] + "\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["UPLOADCALLTRANSCRIPTS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/extensions/calling/transcripts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPLOADCALLTRANSCRIPTS"],
        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=["crm.extensions_calling_transcripts.read"]))
async def get_transcript_by_id(
    context: ToolContext,
    transcript_id: Annotated[
        str, "The unique identifier of the call transcript to retrieve from HubSpot CRM."
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'get-/crm/v3/extensions/calling/transcripts/{transcriptId}'.",
]:
    """Retrieve call transcript details by transcript ID.

    This tool retrieves details of a call transcript for a given transcript ID in the HubSpot CRM. Use it to access specific call records and transcript 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/crm/v3/extensions/calling/transcripts/{transcriptId}".format(  # noqa: UP032
            transcriptId=transcript_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=["crm.extensions_calling_transcripts.write"])
)
async def delete_call_transcript(
    context: ToolContext,
    transcript_id: Annotated[
        str,
        "The unique identifier for the call transcript you want to delete from the HubSpot CRM.",
    ],
) -> Annotated[
    dict[str, Any],
    "Response from the API endpoint 'delete-/crm/v3/extensions/calling/transcripts/{transcriptId}'.",  # noqa: E501
]:
    """Delete a call transcript by transcript ID.

    Removes a call transcript identified by transcript ID from the HubSpot CRM."""
    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/crm/v3/extensions/calling/transcripts/{transcriptId}".format(  # noqa: UP032
            transcriptId=transcript_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=["crm.objects.users.read"]))
async def search_crm_users(
    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-/crm/v3/objects/users/search'."
]:
    """Perform a user search in the CRM database.

    Use this tool to search for users within the HubSpot CRM. It should be called when you need to retrieve user information based on specific criteria.

    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["SEARCHCRMUSERS"],
            "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["SEARCHCRMUSERS"] + "\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["SEARCHCRMUSERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/search",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SEARCHCRMUSERS"],
        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=["crm.objects.users.write"]))
async def create_users_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-/crm/v3/objects/users/batch/create'."
]:
    """Create a batch of users in the CRM system.

    Use this tool to create multiple users at once in the HubSpot CRM. It should be called when there is a need to add multiple users efficiently through a single command.

    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["CREATEUSERSBATCH"],
            "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["CREATEUSERSBATCH"] + "\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["CREATEUSERSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/batch/create",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEUSERSBATCH"],
        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=["crm.objects.users.write"]))
async def archive_users_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-/crm/v3/objects/users/batch/archive'."
]:
    """Archives a batch of users by their IDs in HubSpot CRM.

    Use this tool to archive multiple users at once by providing their IDs to the HubSpot CRM 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["ARCHIVEUSERSBATCH"],
            "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["ARCHIVEUSERSBATCH"] + "\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["ARCHIVEUSERSBATCH"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/batch/archive",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["ARCHIVEUSERSBATCH"],
        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=["crm.objects.users.read"]))
async def retrieve_hubspot_user_records(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    return_only_archived_results: Annotated[
        bool | None,
        "Specify True to return only results that have been archived.  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-/crm/v3/objects/users/batch/read'."
]:
    """Retrieve HubSpot user records by ID or unique property.

    Use this tool to fetch HubSpot CRM user records by providing a record ID or specifying a custom unique value property with the `idProperty` parameter.

    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["RETRIEVEHUBSPOTUSERRECORDS"],
            "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["RETRIEVEHUBSPOTUSERRECORDS"]
                + "\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["RETRIEVEHUBSPOTUSERRECORDS"]
                + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/batch/read",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["RETRIEVEHUBSPOTUSERRECORDS"],
        params=remove_none_values({"archived": return_only_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=["crm.objects.users.write"]))
async def upsert_hubspot_users(
    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-/crm/v3/objects/users/batch/upsert'."
]:
    """Create or update user records in HubSpot CRM.

    This tool is used to create or update user records in HubSpot CRM by specifying a unique property value through the `idProperty` query parameter. It is helpful for maintaining and synchronizing user data in HubSpot.

    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["UPSERTHUBSPOTUSERS"],
            "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["UPSERTHUBSPOTUSERS"] + "\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["UPSERTHUBSPOTUSERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/batch/upsert",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPSERTHUBSPOTUSERS"],
        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=["crm.objects.users.read"]))
async def get_user_info(
    context: ToolContext,
    user_identifier: Annotated[
        str,
        "The unique identifier for the user. This can be the internal object ID or a property value specified by `idProperty`.",  # noqa: E501
    ],
    object_associations: Annotated[
        list[str] | None,
        "Comma separated list of object types to retrieve associated IDs for in the user info response.",  # noqa: E501
    ] = None,
    properties_to_return: Annotated[
        list[str] | None,
        "A list of user properties to return in the response. If any specified properties are not present, they will be ignored.",  # noqa: E501
    ] = None,
    properties_with_history: Annotated[
        list[str] | None, "List of property names to return with their value history for the user."
    ] = None,
    return_only_archived_results: Annotated[
        bool | None,
        "Set to true to return only archived results. Set to false to exclude archived items from results.",  # noqa: E501
    ] = False,
    unique_property_name: Annotated[
        str | None, "The name of a property with unique values to identify the object."
    ] = None,
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/users/{userId}'."
]:
    """Retrieves user information from HubSpot CRM using user ID.

    Use this tool to fetch detailed information about a user in HubSpot CRM by specifying the user's unique ID. You can control the returned data by using specific query parameters."""  # 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/crm/v3/objects/users/{userId}".format(userId=user_identifier),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "properties": properties_to_return,
            "propertiesWithHistory": properties_with_history,
            "associations": object_associations,
            "archived": return_only_archived_results,
            "idProperty": unique_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=["crm.objects.users.write"]))
async def delete_user(
    context: ToolContext,
    user_id_to_delete: Annotated[
        str, "The unique identifier of the user to delete and move to the recycling bin."
    ],
) -> Annotated[
    dict[str, Any], "Response from the API endpoint 'delete-/crm/v3/objects/users/{userId}'."
]:
    """Delete a user and move to recycling bin."""
    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/crm/v3/objects/users/{userId}".format(userId=user_id_to_delete),  # noqa: UP032
        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=["crm.objects.users.write"]))
async def update_hubspot_user(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    user_id: Annotated[
        str | None,
        "The internal user ID or unique property value to identify the user for updating.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    unique_property_name: Annotated[
        str | None,
        "Specifies the name of a property with unique values for identifying the user object. Use this if not using userId.  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-/crm/v3/objects/users/{userId}'."
]:
    """Update user details in HubSpot CRM.

    This tool updates specific user information in HubSpot CRM for a given user ID or a unique property value if specified. Use this tool when you need to modify or clear user property values, bearing in mind read-only and non-existent properties will cause errors.

    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["UPDATEHUBSPOTUSER"],
            "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 user_id:
        missing_params.append(("user_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["UPDATEHUBSPOTUSER"] + "\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["UPDATEHUBSPOTUSER"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/{userId}".format(userId=user_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEHUBSPOTUSER"],
        params=remove_none_values({"idProperty": unique_property_name}),
        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=["crm.objects.users.write"]))
async def update_multiple_users(
    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-/crm/v3/objects/users/batch/update'."
]:
    """Update multiple users in HubSpot CRM by internal ID or unique properties.

    This tool updates a batch of users in HubSpot CRM using either their internal IDs or unique property values. Use this tool to efficiently update information for multiple users 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["UPDATEMULTIPLEUSERS"],
            "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["UPDATEMULTIPLEUSERS"] + "\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["UPDATEMULTIPLEUSERS"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users/batch/update",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLEUSERS"],
        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=["crm.objects.users.read"]))
async def get_users_page(
    context: ToolContext,
    associated_object_types: Annotated[
        list[str] | None,
        "A list of object types for which associated IDs should be retrieved. If specified associations do not exist, they will be ignored.",  # noqa: E501
    ] = None,
    max_results_per_page: Annotated[
        int | None, "Specify the maximum number of results to display per page."
    ] = 10,
    paging_cursor_token: Annotated[
        str | None,
        "The paging cursor token from the last read resource, used to fetch next set of results.",
    ] = None,
    properties_with_history: Annotated[
        list[str] | None,
        "List of properties to return with their history. Reduces the maximum number of users per request.",  # noqa: E501
    ] = None,
    return_only_archived: Annotated[
        bool | None, "Set to true to return only results that have been archived."
    ] = False,
    user_properties: Annotated[
        list[str] | None,
        "A list of user property names to include in the response. Any non-existent properties will be ignored.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'get-/crm/v3/objects/users'."]:
    """Fetch a page of users from the CRM.

    Use this tool to read a page of users from the HubSpot CRM. You can control which user properties are returned by using the appropriate query parameters."""  # 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/crm/v3/objects/users",
        method="GET",
        params=remove_none_values({
            "limit": max_results_per_page,
            "after": paging_cursor_token,
            "properties": user_properties,
            "propertiesWithHistory": properties_with_history,
            "associations": associated_object_types,
            "archived": return_only_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=["crm.objects.users.write"]))
async def create_crm_user(
    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-/crm/v3/objects/users'."]:
    """Create a new user in the CRM and retrieve their ID.

    Use this tool to create a new user in the HubSpot CRM system. The tool will return a detailed user object, including the newly assigned ID. This is useful for adding new users to your CRM database.

    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["CREATECRMUSER"],
            "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["CREATECRMUSER"] + "\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["CREATECRMUSER"] + "\n```"
            ),
        ) from e

    response = await make_request_with_schema_validation(
        url="https://api.hubapi.com/crm/v3/objects/users",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECRMUSER"],
        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}
