"""Arcade Starter Tools for Pylon

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
import jsonschema
from enum import Enum
from typing import Annotated, Any

import httpx

from arcade_tdk import tool, ToolContext
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 as e:
            # 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_secrets=["PYLON_SECRET_TOKEN"])
async def get_account_list(
    context: ToolContext,
    account_fetch_limit: Annotated[
        int,
        "Specify the number of accounts to fetch, from 1 to 999. Defaults to 100 if not specified.",
    ],
    pagination_cursor: Annotated[
        str | None,
        "A string representing the cursor for pagination. Use this to fetch the next set of accounts.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetAccounts'."]:
    """Retrieve a list of accounts from the service.

    This tool fetches a complete list of accounts from the Pylon service. It should be called when you need to obtain account 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.usepylon.com/accounts",
        method="GET",
        params=remove_none_values({"cursor": pagination_cursor, "limit": account_fetch_limit}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_multiple_accounts(
    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 'UpdateAccounts'."]:
    """Update multiple accounts simultaneously.

    Use this tool to update details for multiple accounts in one request. It is helpful for batch processing of account 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["UPDATEMULTIPLEACCOUNTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["UPDATEMULTIPLEACCOUNTS"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/accounts",
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEMULTIPLEACCOUNTS"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_account(
    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 'CreateAccount'."]:
    """Creates a new user account.

    This tool should be called to create a new user account when registration or account opening is requested.

    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["CREATENEWACCOUNT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["CREATENEWACCOUNT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/accounts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATENEWACCOUNT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def search_accounts(
    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 'SearchAccounts'."]:
    """Search and filter accounts using various criteria.

    This tool allows searching and filtering of accounts with specific criteria such as domains, tags, name, external IDs, and custom fields. It supports operators like 'contains', 'equals', 'in', 'not_in', and others, enabling detailed account queries.

    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["SEARCHACCOUNTS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["SEARCHACCOUNTS"] + "\n```"
            ),
        )

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

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


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_account_highlight(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    account_identifier: Annotated[
        str | None,
        "The ID (UUID) or external ID (string) of the account for the highlight.  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 'CreateAccountHighlight'."]:
    """Create a highlight for a specified account.

    This tool is used to create a highlight for an account, using either an internal account ID in UUID format or an external ID in string format. It should be called when there is a need to add or update a highlight for an account.

    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["CREATEACCOUNTHIGHLIGHT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 account_identifier:
        missing_params.append(("account_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["CREATEACCOUNTHIGHLIGHT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/accounts/{account_id}/highlights".format(  # noqa: UP032
            account_id=account_identifier
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEACCOUNTHIGHLIGHT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_account_highlight(
    context: ToolContext,
    account_identifier: Annotated[
        str,
        "The ID or external ID of the account that the highlight belongs to. Can be a UUID or any string.",  # noqa: E501
    ],
    highlight_id: Annotated[str, "The unique ID of the highlight you intend to delete."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteAccountHighlight'."]:
    """Delete a specific highlight from an account.

    Use this tool to delete a specific highlight by providing the account ID and highlight ID. It should be called when a user wants to remove a highlight linked to an 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.usepylon.com/accounts/{account_id}/highlights/{highlight_id}".format(  # noqa: UP032
            account_id=account_identifier, highlight_id=highlight_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_account_highlight(
    context: ToolContext,
    account_identifier: Annotated[
        str,
        "The internal account ID (UUID) or external ID (any string) of the account associated with the highlight.",  # noqa: E501
    ],
    highlight_id: Annotated[
        str,
        "The unique identifier of the highlight you wish to update. Ensure this accurately corresponds to the highlight needing modification.",  # noqa: E501
    ],
    updated_expiration_timestamp: Annotated[
        str | None, "The new expiration timestamp for the highlight in RFC3339 format."
    ] = None,
    updated_html_content: Annotated[
        str | None, "The updated HTML content for the account highlight."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateAccountHighlight'."]:
    """Updates the highlight for a specified account.

    Use this tool to update the highlight information for a given account by specifying either the internal account ID or an external ID. Appropriate when modifications to account highlights are needed."""  # noqa: E501
    request_data: Any = {
        "content_html": updated_html_content,
        "expires_at": updated_expiration_timestamp,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/accounts/{account_id}/highlights/{highlight_id}".format(  # noqa: UP032
            highlight_id=highlight_id, account_id=account_identifier
        ),
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_account(
    context: ToolContext,
    account_id: Annotated[str, "The ID or external ID of the account to delete."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteAccount'."]:
    """Delete an existing account.

    This tool deletes an existing account based on the provided account ID. Call this tool when you need to permanently remove an 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.usepylon.com/accounts/{id}".format(id=account_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_account_info(
    context: ToolContext,
    account_id: Annotated[str, "The ID or external ID of the account to fetch."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetAccount'."]:
    """Retrieve an account's details using its ID or external ID.

    Call this tool to obtain detailed information about an account by providing either the account ID or external 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.usepylon.com/accounts/{id}".format(id=account_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_account(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    account_id: Annotated[
        str | None,
        "The ID or external ID of the account to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    request_body: Annotated[
        str | None,
        "Stringified JSON representing the request body. Required when "
        "mode is 'execute', ignored when mode is 'get_request_schema'",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateAccount'."]:
    """Update details of an existing account.

    Use this tool to modify the information of an existing account by providing the account ID and new details. It should be called when account information needs 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 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["UPDATEACCOUNT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 account_id:
        missing_params.append(("account_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["UPDATEACCOUNT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/accounts/{id}".format(id=account_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEACCOUNT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_account_activity(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    account_id_for_activity: Annotated[
        str | None,
        "The internal or external ID of the account to create the activity for. Accepts a UUID or any string format.  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 'CreateActivity'."]:
    """Creates a new activity for a specified account.

    Use this tool to add a new activity to an account by providing the account's internal or external ID. It will confirm the creation of the activity.

    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["CREATEACCOUNTACTIVITY"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 account_id_for_activity:
        missing_params.append(("account_id_for_activity", "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["CREATEACCOUNTACTIVITY"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/accounts/{id}/activities".format(id=account_id_for_activity),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEACCOUNTACTIVITY"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_custom_activity_types(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetActivityTypes'."]:
    """Retrieve a list of custom activity types.

    Use this tool to get a list of custom activity types that are available. It should be called when there's a need to know what custom activity types exist."""  # 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.usepylon.com/activity-types",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_all_contacts(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetContacts'."]:
    """Retrieve all contacts from the service.

    This tool retrieves all contacts using the Pylon service. It should be called when there's a need to fetch a complete list of contacts from the service."""  # 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.usepylon.com/contacts",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_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 'CreateContact'."]:
    """Create a new contact in the system.

    This tool creates a new contact by sending the necessary details to the endpoint. Use it when you need to add a new person's contact information to the 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["CREATENEWCONTACT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["CREATENEWCONTACT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/contacts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATENEWCONTACT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
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 'SearchContacts'."]:
    """Search for contacts using various filter criteria.

    This tool allows you to search for contacts by applying filters on fields such as id, email, custom fields, and account_id. Use it to find specific contacts based on these 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(
        url="https://api.usepylon.com/contacts/search",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_contact(
    context: ToolContext,
    contact_id: Annotated[str, "The ID of the contact to be deleted."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteContact'."]:
    """Delete an existing contact by ID.

    This tool deletes a contact by its ID. It should be called when a user needs to remove a contact 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.usepylon.com/contacts/{id}".format(id=contact_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_contact_by_id(
    context: ToolContext,
    contact_id: Annotated[str, "The unique identifier for the contact to fetch details."],
    number_of_accounts_to_fetch: Annotated[
        int,
        "Specifies the number of accounts to fetch. Must be between 1 and 999. Defaults to 100 if not provided.",  # noqa: E501
    ],
    pagination_cursor: Annotated[
        str | None,
        "A string representing the cursor for pagination purposes, used to fetch the next set of results in a paginated response.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetContact'."]:
    """Retrieve contact details using the contact ID.

    This tool fetches and returns contact details by querying with a specified contact ID. Use it when you need to obtain specific contact 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.usepylon.com/contacts/{id}".format(id=contact_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({
            "cursor": pagination_cursor,
            "limit": number_of_accounts_to_fetch,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_contact_info(
    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 identifier of the contact to be updated. This should match the existing contact's ID in the system.  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 'UpdateContact'."]:
    """Updates an existing contact's information.

    Use this tool to update the details of an existing contact when modifications are needed.

    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["UPDATECONTACTINFO"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 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["UPDATECONTACTINFO"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/contacts/{id}".format(id=contact_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECONTACTINFO"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_all_custom_fields(
    context: ToolContext,
    custom_field_object_type: Annotated[
        str, 'Specify the object type for custom fields. Options: "account", "issue", or "contact".'
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetCustomFields'."]:
    """Retrieve all custom fields from the system.

    This tool is used to get a complete list of custom fields. It should be called when you need information about available custom fields."""  # 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.usepylon.com/custom-fields",
        method="GET",
        params=remove_none_values({"object_type": custom_field_object_type}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_custom_field(
    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 'CreateCustomField'."]:
    """Create a new custom field in the system.

    Use this tool to add a custom field to your database or application configuration. It allows for extending the available data fields to include additional, personalized 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["CREATECUSTOMFIELD"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["CREATECUSTOMFIELD"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/custom-fields",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATECUSTOMFIELD"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_custom_field(
    context: ToolContext,
    custom_field_id: Annotated[str, "The unique identifier of the custom field to retrieve."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetCustomField'."]:
    """Retrieve a custom field by its ID.

    Use this tool to obtain details of a specific custom field using its unique identifier."""
    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.usepylon.com/custom-fields/{id}".format(id=custom_field_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_custom_field(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    custom_field_id: Annotated[
        str | None,
        "The unique identifier for the custom field 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 'UpdateCustomField'."]:
    """Update a custom field in a record.



    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["UPDATECUSTOMFIELD"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 custom_field_id:
        missing_params.append(("custom_field_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["UPDATECUSTOMFIELD"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/custom-fields/{id}".format(id=custom_field_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATECUSTOMFIELD"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def import_new_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 'ImportContact'."]:
    """Import a new contact into the system.

    Use this tool to import a new contact into the system whenever you need to add 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["IMPORTNEWCONTACT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["IMPORTNEWCONTACT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/import/contacts",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["IMPORTNEWCONTACT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def import_issue(
    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 'ImportIssue'."]:
    """Import an issue into the system.

    This tool is used to import an issue into the Pylon system. Use it when you need to add new issues by importing them through a specified format or integration.

    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["IMPORTISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["IMPORTISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/import/issues",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["IMPORTISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def import_messages_to_issue(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The unique ID of the existing issue to which messages will be imported.  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 'ImportMessages'."]:
    """Import messages into an existing issue.

    Use this tool to import new messages onto an existing issue, facilitating better organization and tracking within issue management systems.

    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["IMPORTMESSAGESTOISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["IMPORTMESSAGESTOISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/import/issues/{id}/messages".format(id=issue_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["IMPORTMESSAGESTOISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issue_statuses(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssueStatuses'."]:
    """Retrieve all issue statuses from the system.

    Use this tool to get a complete list of issue statuses available in the project management system. This can help with tracking and managing project workflows 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.usepylon.com/issue-statuses",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issues_list(
    context: ToolContext,
    end_time_rfc3339: Annotated[
        str,
        "The end time in RFC3339 format for the issue retrieval range, must be within 30 days of the start time.",  # noqa: E501
    ],
    start_time_for_issue_range: Annotated[
        str,
        "The start time in RFC3339 format to query issues. Duration with end_time must be <= 30 days.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssues'."]:
    """Retrieve a list of issues.

    Call this tool to get a list of issues from the Pylon service, useful for tracking or managing tasks and bugs."""  # 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.usepylon.com/issues",
        method="GET",
        params=remove_none_values({
            "start_time": start_time_for_issue_range,
            "end_time": end_time_rfc3339,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_issue(
    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 'CreateIssue'."]:
    """Create a new issue in the system.

    This tool is used to create a new issue in the system. It should be called whenever there's a need to log or track a new issue or task.

    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["CREATENEWISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["CREATENEWISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues",
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATENEWISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def search_issues_by_filters(
    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 'SearchIssues'."]:
    """Search for issues using various filters and criteria.

    Use this tool to search for issues by applying filters such as creation date, account ID, requester ID, status, tags, title, and more. Suitable for retrieving specific issues according to predefined criteria and operators.

    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["SEARCHISSUESBYFILTERS"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["SEARCHISSUESBYFILTERS"] + "\n```"
            ),
        )

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

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


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_issue(
    context: ToolContext,
    issue_id: Annotated[str, "The unique identifier of the issue to delete."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteIssue'."]:
    """Delete an existing issue by ID.

    Use this tool to delete an issue from the system by providing its ID. It is ideal for scenarios where issues need to be managed or cleaned up."""  # 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.usepylon.com/issues/{id}".format(id=issue_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issue_by_id(
    context: ToolContext,
    issue_id: Annotated[str, "The unique ID or number of the issue you want to retrieve."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssue'."]:
    """Retrieve issue details using its ID or number.

    Use this tool to access details of an issue by providing its specific ID or number. Ideal for tasks requiring detailed issue information retrieval in issue tracking systems."""  # 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.usepylon.com/issues/{id}".format(id=issue_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_issue(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The ID of the issue to update. This should be a string representing the unique identifier for the issue.  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 'UpdateIssue'."]:
    """Update details of an existing issue.

    Use this tool to modify information for an already existing issue when changes or corrections are needed.

    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["UPDATEISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["UPDATEISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}".format(id=issue_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_ai_response_for_issue(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The unique ID or number of the issue for which an AI response is required.  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 'CreateIssueAIResponse'."]:
    """Generate an AI response for a specific issue.

    This tool creates an AI-generated response for a given issue by specifying the issue ID. It should be called when an automated response is required for issue resolution or documentation.

    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["CREATEAIRESPONSEFORISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["CREATEAIRESPONSEFORISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}/ai-response".format(id=issue_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEAIRESPONSEFORISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def link_external_issue(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_identifier: Annotated[
        str | None,
        "The unique ID or number of the internal issue to which external issues should be linked or unlinked.  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 'LinkExternalIssue'."]:
    """Link or unlink external issues to an internal issue.

    Use this tool to link external issues to an internal issue or unlink them by specifying the operation type. Ideal for managing issue relationships between different systems.

    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["LINKEXTERNALISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_identifier:
        missing_params.append(("issue_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["LINKEXTERNALISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}/external-issues".format(id=issue_identifier),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["LINKEXTERNALISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issue_followers(
    context: ToolContext,
    issue_id: Annotated[str, "The unique ID or number of the issue to retrieve followers for."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssueFollowers'."]:
    """Get followers of a specific issue to manage participation.

    This tool retrieves the list of users who are following a specified issue. Use this tool when you need to know who is engaging with or interested in updates about an issue."""  # 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.usepylon.com/issues/{id}/followers".format(id=issue_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_issue_followers(
    context: ToolContext,
    issue_id: Annotated[str, "The ID or number of the issue to add or remove followers from."],
    contact_ids_to_add: Annotated[
        list[str] | None,
        "An array of contact IDs to add as followers to the issue. Each ID should be a string.",
    ] = None,
    update_operation: Annotated[
        str | None, "Specify 'add' to add followers or 'remove' to remove them."
    ] = None,
    user_ids_to_add_as_followers: Annotated[
        list[str] | None, "List of user IDs to add as followers to a specific issue."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'AddIssueFollowers'."]:
    """Add or remove followers for an issue.

    This tool allows you to add or remove followers to a specific issue. Use it when managing the users who should receive notifications about updates on an issue. Set the operation field to \"remove\" to remove followers."""  # noqa: E501
    request_data: Any = {
        "contact_ids": contact_ids_to_add,
        "operation": update_operation,
        "user_ids": user_ids_to_add_as_followers,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/issues/{id}/followers".format(id=issue_id),  # noqa: UP032
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issue_messages(
    context: ToolContext,
    issue_id: Annotated[str, "The unique ID of the issue for which to retrieve messages."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssueMessages'."]:
    """Retrieve messages for a specific issue.

    This tool is used to obtain all messages associated with a particular issue, identified by its 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.usepylon.com/issues/{id}/messages".format(id=issue_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def redact_message(
    context: ToolContext,
    issue_id: Annotated[str, "The ID of the issue associated with the message to be redacted."],
    message_id: Annotated[str, "The unique identifier of the message to be redacted."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'RedactMessage'."]:
    """Removes or hides the content of a specified message.

    Use this tool to redact (remove or hide) the content of a message identified by a specific message ID within an issue. This is useful for maintaining privacy or correcting errors."""  # 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.usepylon.com/issues/{id}/messages/{message_id}/redact".format(  # noqa: UP032
            id=issue_id, message_id=message_id
        ),
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_issue_note(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The unique identifier of the issue to which the note will be added.  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 'CreateIssueNote'."]:
    """Creates a note on an issue to track additional details.

    This tool should be called when there's a need to add a note or comment to an existing issue to provide further information or updates.

    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["CREATEISSUENOTE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["CREATEISSUENOTE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}/note".format(id=issue_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEISSUENOTE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_issue_reply(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The unique identifier of the issue to which you want to reply.  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 'CreateIssueReply'."]:
    """Create a reply to an existing issue.

    Use this tool to add a comment or response to an existing issue when interaction or follow-up is required.

    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["CREATEISSUEREPLY"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["CREATEISSUEREPLY"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}/reply".format(id=issue_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEISSUEREPLY"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def snooze_issue(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    issue_id: Annotated[
        str | None,
        "The ID or number of the issue to snooze.  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 'SnoozeIssue'."]:
    """Temporarily pause notifications for an issue.

    Use this tool to snooze notifications for a specific issue temporarily. It is helpful when you want to focus elsewhere without getting alerts for the issue.

    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["SNOOZEISSUE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 issue_id:
        missing_params.append(("issue_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["SNOOZEISSUE"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/issues/{id}/snooze".format(id=issue_id),  # noqa: UP032
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["SNOOZEISSUE"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_issue_threads(
    context: ToolContext,
    issue_id: Annotated[str, "The unique ID of the issue for which threads need to be retrieved."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetIssueThreads'."]:
    """Retrieve threads for a specific issue.

    Call this tool to get all threads associated with a particular issue using its ID. Useful for tracking conversations or discussions related to an issue."""  # 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.usepylon.com/issues/{id}/threads".format(id=issue_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_issue_thread(
    context: ToolContext,
    issue_id: Annotated[
        str,
        "The ID for the issue you want to create a thread for. Required to link the thread to the correct issue.",  # noqa: E501
    ],
    thread_name: Annotated[
        str | None,
        "The title or name of the thread to be created. This should be descriptive of the discussion purpose.",  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateIssueThread'."]:
    """Create an issue thread for an existing issue.

    Use this tool to initiate a discussion thread related to a specific issue by providing the issue ID. This helps in organizing conversations around particular issues efficiently."""  # noqa: E501
    request_data: Any = {"name": thread_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.usepylon.com/issues/{id}/threads".format(id=issue_id),  # noqa: UP032
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_knowledge_bases(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetKnowledgeBases'."]:
    """Retrieve all available knowledge bases.

    This tool is called to get a list of all knowledge bases from the Pylon service."""
    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.usepylon.com/knowledge-bases",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_knowledge_base_by_id(
    context: ToolContext,
    knowledge_base_id: Annotated[
        str, "The unique identifier for the knowledge base to be retrieved."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetKnowledgeBase'."]:
    """Retrieve a knowledge base using its ID.

    Use this tool to fetch information about a specific knowledge base by providing 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.usepylon.com/knowledge-bases/{id}".format(id=knowledge_base_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_knowledge_base_article(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    knowledge_base_id: Annotated[
        str | None,
        "The unique identifier for the knowledge base where the article will be added.  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 'CreateArticle'."]:
    """Create and publish an article in a knowledge base.

    This tool allows the creation of a new article in a specified knowledge base, including optional translations. If the article is set to publish, translations will also be published.

    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["CREATEKNOWLEDGEBASEARTICLE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 knowledge_base_id:
        missing_params.append(("knowledge_base_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["CREATEKNOWLEDGEBASEARTICLE"]
                + "\n```"
            ),
        )

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

    response = await make_request(
        url="https://api.usepylon.com/knowledge-bases/{id}/articles".format(id=knowledge_base_id),  # noqa: UP032
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_article(
    context: ToolContext,
    article_id: Annotated[
        str, "The ID of the article that needs to be deleted from the knowledge base."
    ],
    knowledge_base_id: Annotated[str, "The KnowledgeBaseID of the article to delete."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteArticle'."]:
    """Delete an existing article from a knowledge base.

    Call this tool to remove an article from a knowledge base by specifying the knowledge base and article IDs."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/knowledge-bases/{id}/articles/{article_id}".format(  # noqa: UP032
            id=knowledge_base_id, article_id=article_id
        ),
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_article(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    knowledge_base_id: Annotated[
        str | None,
        "The identifier for the knowledge base where the article to be updated is located.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    article_id: Annotated[
        str | None,
        "Specify the unique identifier of the article you want to update in the knowledge base.  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 'UpdateArticle'."]:
    """Update the content of an existing article.

    Use this tool to modify the content or details of an existing article in a knowledge base when updates are needed.

    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["UPDATEARTICLE"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 knowledge_base_id:
        missing_params.append(("knowledge_base_id", "path"))
    if not article_id:
        missing_params.append(("article_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["UPDATEARTICLE"] + "\n```"
            ),
        )

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

    response = await make_request(
        url="https://api.usepylon.com/knowledge-bases/{id}/articles/{article_id}".format(  # noqa: UP032
            id=knowledge_base_id, article_id=article_id
        ),
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=json.dumps(request_data),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def list_collections_in_knowledge_base(
    context: ToolContext,
    knowledge_base_id: Annotated[str, "The ID of the knowledge base to list the collections from."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetCollections'."]:
    """List all collections in a knowledge base.

    Use this tool to retrieve all collections within a specific knowledge base. Useful for accessing organized information in structured formats."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/knowledge-bases/{id}/collections".format(  # noqa: UP032
            id=knowledge_base_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_collection(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    knowledge_base_id: Annotated[
        str | None,
        "The unique identifier for the knowledge base where the new collection 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 'CreateCollection'."]:
    """Create a new collection in a knowledge base.

    This tool is used to create a new collection within a specified knowledge base by providing the knowledge base 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["CREATENEWCOLLECTION"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 knowledge_base_id:
        missing_params.append(("knowledge_base_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["CREATENEWCOLLECTION"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/knowledge-bases/{id}/collections".format(  # noqa: UP032
            id=knowledge_base_id
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATENEWCOLLECTION"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_collection_by_id(
    context: ToolContext,
    collection_id: Annotated[
        str, "The unique ID of the collection to be retrieved from the knowledge base."
    ],
    knowledge_base_id: Annotated[
        str, "The ID of the knowledge base to which the collection belongs."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetCollection'."]:
    """Retrieve a specific collection using its ID.

    Use this tool to obtain detailed information about a particular collection by supplying the collection ID. The collection belongs to a specific knowledge base. Ideal for retrieving specific datasets or information collections within larger systems."""  # 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.usepylon.com/knowledge-bases/{id}/collections/{collection_id}".format(  # noqa: UP032
            id=knowledge_base_id, collection_id=collection_id
        ),
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_route_redirect(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    knowledge_base_id: Annotated[
        str | None,
        "The unique identifier for the knowledge base where the route redirect will be added.  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 'CreateRouteRedirect'."]:
    """Create a new route redirect for a knowledge base.

    This tool is used to create a new route redirect within a specified knowledge base. It should be called when you need to add a redirect to manage or alter the routing within a knowledge base.

    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["CREATEROUTEREDIRECT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 knowledge_base_id:
        missing_params.append(("knowledge_base_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["CREATEROUTEREDIRECT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/knowledge-bases/{id}/route-redirects".format(  # noqa: UP032
            id=knowledge_base_id
        ),
        method="POST",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["CREATEROUTEREDIRECT"],
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_organization_details(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetMe'."]:
    """Retrieve details of the current organization.

    Use this tool to get information about the organization linked to the given API token, like name, address, and other relevant 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.usepylon.com/me",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_milestone(
    context: ToolContext,
    milestone_name: Annotated[
        str,
        "The name of the milestone to be created. This is the title that will be displayed for the milestone.",  # noqa: E501
    ],
    project_id_for_milestone: Annotated[
        str,
        "The unique identifier for the project to which this milestone belongs. Required for associating the milestone with the correct project.",  # noqa: E501
    ],
    account_id_for_project: Annotated[
        str | None,
        "Account ID for the project. This is needed to associate the milestone with the correct account.",  # noqa: E501
    ] = None,
    milestone_due_date: Annotated[
        str | None, "Due date for this milestone in RFC 3339 format (e.g., 2023-12-31T23:59:59Z)."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateMilestone'."]:
    """Create a new milestone in a project management system.

    This tool is used to create a milestone within a project management application. It should be called when there's a need to add a new milestone to track progress or set goals in a project. The tool confirms the creation of the milestone."""  # 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.usepylon.com/milestones",
        method="POST",
        params=remove_none_values({
            "name": milestone_name,
            "due_date": milestone_due_date,
            "project_id": project_id_for_milestone,
            "account_id": account_id_for_project,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_milestone(
    context: ToolContext,
    milestone_id: Annotated[
        str,
        "The unique ID of the milestone to be deleted. Ensure it is correct as this action is irreversible.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteMilestone'."]:
    """Delete a specific milestone using its ID.

    Use this tool to remove a milestone by providing its unique ID. This operation is irreversible and should be used when a milestone is no longer 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.usepylon.com/milestones/{id}".format(id=milestone_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_milestone(
    context: ToolContext,
    milestone_id: Annotated[str, "The unique ID of the milestone to update."],
    milestone_due_date: Annotated[
        str | None,
        "The due date for the milestone in RFC 3339 format (e.g., 2023-10-21T00:00:00Z).",
    ] = None,
    milestone_name: Annotated[
        str | None, "The new name for the milestone you want to update."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateMilestone'."]:
    """Updates the details of a specific milestone.

    This tool is used to update information for a specific milestone in a project, identified by its ID."""  # noqa: E501
    request_data: Any = {"due_date": milestone_due_date, "name": milestone_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.usepylon.com/milestones/{id}".format(id=milestone_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_project(
    context: ToolContext,
    account_id_for_project: Annotated[
        str,
        "Account ID associated with the project. This identifies the account under which the project will be created.",  # noqa: E501
    ],
    project_name: Annotated[
        str,
        "The name for the new project. This should be a descriptive and unique name to easily identify the project.",  # noqa: E501
    ],
    customer_portal_visible: Annotated[
        bool | None,
        "Boolean indicating if the project should be visible in the customer portal. True makes it visible.",  # noqa: E501
    ] = False,
    project_description_html: Annotated[
        str | None,
        "HTML formatted description for the project, including any necessary tags and formatting.",
    ] = None,
    project_end_date: Annotated[
        str | None,
        "End date for the project in RFC 3339 format. Indicates when the project is expected to be finished.",  # noqa: E501
    ] = None,
    project_owner_id: Annotated[
        str | None,
        "The unique identifier for the owner of the project. This is a string that specifies who will be managing or leading the project.",  # noqa: E501
    ] = None,
    project_template_id: Annotated[
        str | None,
        "The ID of the template to be used for creating this project. It should match an existing project template ID.",  # noqa: E501
    ] = None,
    start_date: Annotated[
        str | None, "Start date for the project in RFC 3339 format (e.g., 2023-03-10T14:00:00Z)."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateProject'."]:
    """Create a new project seamlessly.

    This tool is used to create a new project. It should be called when a user wants to initialize or start a new project within a 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.usepylon.com/projects",
        method="POST",
        params=remove_none_values({
            "name": project_name,
            "description_html": project_description_html,
            "customer_portal_visible": customer_portal_visible,
            "start_date": start_date,
            "end_date": project_end_date,
            "account_id": account_id_for_project,
            "owner_id": project_owner_id,
            "project_template_id": project_template_id,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_existing_project(
    context: ToolContext,
    project_id: Annotated[str, "The unique identifier of the project to be deleted."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteProject'."]:
    """Delete an existing project using its ID.

    Use this tool to delete an existing project by providing the project ID. It allows you to manage and remove projects that are no longer 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.usepylon.com/projects/{id}".format(id=project_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_project(
    context: ToolContext,
    mode: Annotated[
        ToolMode,
        "Operation mode: 'get_request_schema' returns the OpenAPI spec "
        "for the request body, 'execute' performs the actual operation",
    ],
    project_id: Annotated[
        str | None,
        "The unique identifier of the project to update.  Required when mode is 'execute', ignored when mode is 'get_request_schema'.",  # noqa: E501
    ] = None,
    project_name: Annotated[
        str | None,
        "The new name for this project. Provide a descriptive title to identify the project.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    project_description_html: Annotated[
        str | None, "HTML formatted description for the project.  Only used when mode is 'execute'."
    ] = None,
    start_date_rfc3339: Annotated[
        str | None,
        "The start date for the project, formatted in RFC 3339. This specifies when the project begins.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    project_end_date: Annotated[
        str | None,
        "The end date for the project in RFC 3339 format.  Only used when mode is 'execute'.",
    ] = None,
    project_owner_id: Annotated[
        str | None,
        "The unique identifier of the owner of this project. Required to assign an owner when updating a project.  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    is_customer_portal_visible: Annotated[
        bool | None,
        "Indicate if the project should be visible in the customer portal (true for visible, false for hidden).  Only used when mode is 'execute'.",  # noqa: E501
    ] = None,
    is_project_archived: Annotated[
        bool | None,
        "Set to True if the project is archived, False if it is active.  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 'UpdateProject'."]:
    """Update the details of an existing project.

    Use this tool to modify the information of an existing project by providing the project ID and necessary updates.

    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["UPDATEPROJECT"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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 project_id:
        missing_params.append(("project_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["UPDATEPROJECT"] + "\n```"
            ),
        )

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

    response = await make_request_with_schema_validation(
        url="https://api.usepylon.com/projects/{id}".format(id=project_id),  # noqa: UP032
        method="PATCH",
        request_data=request_data,
        schema=REQUEST_BODY_SCHEMAS["UPDATEPROJECT"],
        params=remove_none_values({
            "name": project_name,
            "description_html": project_description_html,
            "customer_portal_visible": is_customer_portal_visible,
            "start_date": start_date_rfc3339,
            "end_date": project_end_date,
            "owner_id": project_owner_id,
            "is_archived": is_project_archived,
        }),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def fetch_all_tags(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTags'."]:
    """Retrieve all available tags.

    Use this tool to obtain a comprehensive list of all tags. It is ideal for scenarios where you need to display or manage tags."""  # 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.usepylon.com/tags",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_tag(
    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 'CreateTag'."]:
    """Create a new tag within the system.

    This tool is used to create a new tag by sending a POST request to the /tags endpoint. It should be called when a user wants to add a new tag to the system for organizational or categorization 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["CREATENEWTAG"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["CREATENEWTAG"] + "\n```"
            ),
        )

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

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


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_existing_tag(
    context: ToolContext,
    tag_id: Annotated[
        str,
        "The unique identifier of the tag to delete. Provide this ID to specify which tag should be removed.",  # noqa: E501
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteTag'."]:
    """Delete an existing tag by ID.

    Use this tool to delete an existing tag by specifying its ID. It confirms whether the deletion was successful."""  # 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.usepylon.com/tags/{id}".format(id=tag_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_tag_by_id(
    context: ToolContext,
    tag_id: Annotated[str, "The unique identifier for the tag to retrieve details."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTag'."]:
    """Retrieve tag details using a specified 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.usepylon.com/tags/{id}".format(id=tag_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_existing_tag(
    context: ToolContext,
    tag_id: Annotated[str, "The unique identifier of the tag that needs to be updated."],
    tag_hex_color: Annotated[
        str | None,
        "The hex code representing the tag's color. Must be a valid six-digit hexadecimal value prefixed with '#'.",  # noqa: E501
    ] = None,
    tag_name: Annotated[str | None, "The new name for the tag to be updated."] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateTag'."]:
    """Update an existing tag's information.

    Use this tool to modify the details of an already existing tag. It should be called when changes to tag attributes are needed."""  # noqa: E501
    request_data: Any = {"hex_color": tag_hex_color, "value": tag_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.usepylon.com/tags/{id}".format(id=tag_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_task(
    context: ToolContext,
    task_title: Annotated[
        str,
        "The title of the task to be created. It should summarize the task clearly and concisely.",
    ],
    account_id_for_task: Annotated[
        str | None, "Specify the Account ID associated with this task."
    ] = None,
    make_customer_portal_visible: Annotated[
        bool | None,
        "Set to true to make the task visible on the customer portal, false to hide it.",
    ] = False,
    milestone_id: Annotated[
        str | None, "Unique identifier for the milestone associated with the task."
    ] = None,
    project_id: Annotated[
        str | None, "Unique identifier for the project to which this task belongs."
    ] = None,
    task_assignee_id: Annotated[
        str | None,
        "The ID of the user to whom the task is assigned. It should be a valid user ID in the system.",  # noqa: E501
    ] = None,
    task_body_html: Annotated[
        str | None,
        "HTML content for the task body. Provide detailed information or instructions here using HTML formatting.",  # noqa: E501
    ] = None,
    task_due_date: Annotated[
        str | None,
        "Due date for the task, formatted in RFC 3339. Specify the date by which the task should be completed.",  # noqa: E501
    ] = None,
    task_status: Annotated[
        str | None, "Set the task's current status: 'not_started', 'in_progress', or 'completed'."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateTask'."]:
    """Create a new task with specified parameters.

    Use this tool to create a task. It should be called when a new task needs to be added to the system, specifying necessary 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.usepylon.com/tasks",
        method="POST",
        params=remove_none_values({
            "title": task_title,
            "body_html": task_body_html,
            "milestone_id": milestone_id,
            "status": task_status,
            "customer_portal_visible": make_customer_portal_visible,
            "due_date": task_due_date,
            "assignee_id": task_assignee_id,
            "account_id": account_id_for_task,
            "project_id": project_id,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_task(
    context: ToolContext,
    task_id: Annotated[str, "The unique identifier of the task to be deleted."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteTask'."]:
    """Delete an existing task by ID.

    Use this tool to delete a specific task by its ID. Call it when you need to remove a task permanently 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.usepylon.com/tasks/{id}".format(id=task_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_task(
    context: ToolContext,
    task_id: Annotated[str, "The unique ID of the task you want to update."],
    assignee_id: Annotated[
        str | None,
        "The ID of the person assigned to this task. It should be a string representing a valid user ID within the system.",  # noqa: E501
    ] = None,
    customer_portal_visible: Annotated[
        bool | None,
        "Set to true to make the task visible on the customer portal. Accepts a boolean.",
    ] = None,
    milestone_identifier: Annotated[
        str | None, "Specify the Milestone ID associated with the task to update its milestone."
    ] = None,
    project_id: Annotated[
        str | None,
        "The unique identifier of the project associated with this task. Required to specify which project's task is being updated.",  # noqa: E501
    ] = None,
    task_body_html: Annotated[
        str | None,
        "HTML content for the task's body. This defines the main description or details of the task.",  # noqa: E501
    ] = None,
    task_due_date: Annotated[
        str | None, "Due date for the task in RFC 3339 format, e.g., '2023-12-31T23:59:59Z'."
    ] = None,
    task_status: Annotated[
        str | None, "Specify the task status: `not_started`, `in_progress`, or `completed`."
    ] = None,
    task_title: Annotated[
        str | None,
        "The new title for the task. Provide a clear and concise title for better understanding.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateTask'."]:
    """Update the details of an existing task.

    Use this tool to modify the information of a specific task by providing its ID. Ideal for updating any details like status, description, or due date."""  # noqa: E501
    request_data: Any = {
        "assignee_id": assignee_id,
        "body_html": task_body_html,
        "customer_portal_visible": customer_portal_visible,
        "due_date": task_due_date,
        "milestone_id": milestone_identifier,
        "project_id": project_id,
        "status": task_status,
        "title": task_title,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/tasks/{id}".format(id=task_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def fetch_team_list(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTeams'."]:
    """Retrieve the list of available teams.

    Use this tool to get a list of all teams from the service. Useful for displaying team information or selecting a team from the 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.usepylon.com/teams",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_new_team(
    context: ToolContext,
    team_name: Annotated[str | None, "The name to assign to the newly created team."] = None,
    user_ids_to_add: Annotated[
        list[str] | None,
        "A list of user IDs to include in the new team. Each user ID should be a string.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateTeam'."]:
    """Create a new team for collaboration.

    Use this tool to create a new team on the platform, facilitating collaboration and organization. It returns details about the newly created team."""  # noqa: E501
    request_data: Any = {"name": team_name, "user_ids": user_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.usepylon.com/teams",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_team_by_id(
    context: ToolContext,
    team_id: Annotated[str, "The unique ID of the team to retrieve details for."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTeam'."]:
    """Retrieve team details using the team's ID.

    Use this tool to get detailed information about a team by specifying its unique 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.usepylon.com/teams/{id}".format(id=team_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_existing_team(
    context: ToolContext,
    team_id: Annotated[str, "The ID of the team to update."],
    team_member_user_ids: Annotated[
        list[str] | None,
        "The list of user IDs to set as team members. This updates the team to include only these users.",  # noqa: E501
    ] = None,
    team_name: Annotated[str | None, "The new name for the team to be updated."] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateTeam'."]:
    """Update details of an existing team.

    Use this tool to modify the information of an existing team. Ideal for updating team names, members, or other details."""  # noqa: E501
    request_data: Any = {"name": team_name, "user_ids": team_member_user_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.usepylon.com/teams/{id}".format(id=team_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_ticket_forms(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTicketForms'."]:
    """Retrieve a list of ticket forms available.

    This tool fetches a list of ticket forms, which can be used to understand the structure of available forms for managing tickets. It should be called when you need to review or select from ticket forms."""  # 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.usepylon.com/ticket-forms",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_ticket_form(
    context: ToolContext,
    ticket_form_id: Annotated[str, "The unique ID of the ticket form to retrieve."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTicketForm'."]:
    """Retrieve detailed information about a ticket form.

    Call this tool to obtain detailed information for a specific ticket form by providing 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.usepylon.com/ticket-forms/{id}".format(id=ticket_form_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def list_training_data(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'ListTrainingData'."]:
    """Fetches all training data configurations for the organization.

    Use this tool to retrieve a comprehensive list of training data configurations associated with the organization. Ideal for managing or reviewing training datasets."""  # 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.usepylon.com/training-data",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def create_training_data_config(
    context: ToolContext,
    training_data_name: Annotated[
        str | None, "The name of the training data container to be created."
    ] = None,
    training_data_visibility: Annotated[
        str | None,
        'Specifies who can access the training data. Valid options: "everyone", "user_only", "ai_agent_only". Defaults to "everyone".',  # noqa: E501
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'CreateTrainingData'."]:
    """Create a new training data configuration.

    Call this tool to set up a new training data configuration for your application. It is used when you need to initialize or update your training data setup."""  # noqa: E501
    request_data: Any = {
        "training_data_name": training_data_name,
        "visibility": training_data_visibility,
    }
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/training-data",
        method="POST",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def retrieve_training_data_configuration(
    context: ToolContext,
    training_data_id: Annotated[
        str, "The unique ID of the training data to retrieve its configuration."
    ],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetTrainingData'."]:
    """Retrieve a training data configuration by ID.

    This tool retrieves the details of a specific training data configuration using its unique ID. It should be called when you need to access the configuration settings or specifications for a particular training dataset."""  # 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.usepylon.com/training-data/{id}".format(id=training_data_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def delete_training_data_documents(
    context: ToolContext,
    training_data_id: Annotated[
        str,
        "The ID of the training data from which documents will be deleted. Provide this to specify the exact dataset.",  # noqa: E501
    ],
    document_ids_to_delete: Annotated[
        list[str] | None,
        "An array of document IDs that need to be deleted from the training data configuration.",
    ] = None,
    external_ids_to_delete: Annotated[
        list[str] | None, "A list of external IDs to delete from the training data configuration."
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'DeleteTrainingDataDocuments'."]:
    """Deletes documents from a training data configuration.

    Use this tool to delete documents from a specific training data configuration by providing the relevant ID. This tool is helpful when managing or updating data sets for training purposes."""  # noqa: E501
    request_data: Any = {}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/training-data/{id}/documents".format(id=training_data_id),  # noqa: UP032
        method="DELETE",
        params=remove_none_values({
            "document_ids": document_ids_to_delete,
            "external_ids": external_ids_to_delete,
        }),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_user_roles(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetUserRoles'."]:
    """Get a list of all user roles.

    Use this tool to retrieve a comprehensive list of all user roles. It is useful for managing and understanding user permissions."""  # 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.usepylon.com/user-roles",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def list_users(
    context: ToolContext,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetUsers'."]:
    """Fetches a list of users.

    Use this tool to retrieve a list of all users. It provides an overview of the users available in 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.usepylon.com/users",
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def search_users_by_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 'SearchUsers'."]:
    """Search for users by email with filter options.

    Use this tool to find users by filtering their email with options such as 'equals', 'in', or 'not_in'. It should be called when there's a need to retrieve user information based on specific email 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["SEARCHUSERSBYEMAIL"],
            "instructions": (
                "Use the request_body_schema to construct a valid JSON object. "
                "Once you have populated 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["SEARCHUSERSBYEMAIL"] + "\n```"
            ),
        )

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

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


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def get_user_by_id(
    context: ToolContext,
    user_id: Annotated[str, "The unique ID of the user to fetch details for."],
) -> Annotated[dict[str, Any], "Response from the API endpoint 'GetUser'."]:
    """Retrieve user details using their unique ID.

    Use this tool to get detailed information about a user by providing their unique 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.usepylon.com/users/{id}".format(id=user_id),  # noqa: UP032
        method="GET",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            )
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}


@tool(requires_secrets=["PYLON_SECRET_TOKEN"])
async def update_user_profile(
    context: ToolContext,
    user_id: Annotated[str, "The unique identifier of the user to be updated."],
    user_role_id: Annotated[str | None, "The role ID to assign to the user."] = None,
    user_status: Annotated[
        str | None,
        "The updated status for the user. Options: `active`, `away`, or `out_of_office`.",
    ] = None,
) -> Annotated[dict[str, Any], "Response from the API endpoint 'UpdateUser'."]:
    """Update a user's profile information.

    This tool updates the information of a user identified by a specific ID. It should be called when changes to a user's profile are needed, such as updating personal details or contact information."""  # noqa: E501
    request_data: Any = {"role_id": user_role_id, "status": user_status}
    if isinstance(request_data, dict):
        request_data = remove_none_values(request_data)
    content = json.dumps(request_data) if request_data else None
    response = await make_request(
        url="https://api.usepylon.com/users/{id}".format(id=user_id),  # noqa: UP032
        method="PATCH",
        params=remove_none_values({}),
        headers=remove_none_values({
            "Content-Type": "application/json",
            "Authorization": "Bearer {authorization}".format(  # noqa: UP032
                authorization=context.get_secret("PYLON_SECRET_TOKEN")
            ),
        }),
        content=content,
    )
    try:
        return {"response_json": response.json()}
    except Exception:
        return {"response_text": response.text}
