import { chat, chat_v1 } from '@googleapis/chat'; import { Logger, BaseFormatConverter, Root, CardElement, Adapter, ChatInstance, StateAdapter, UserInfo, WebhookOptions, Message, AdapterPostableMessage, RawMessage, EphemeralMessage, Attachment, EmojiValue, FetchOptions, FetchResult, ThreadInfo, ListThreadsOptions, ListThreadsResult, ChannelInfo, FormattedContent } from 'chat'; import { workspaceevents } from '@googleapis/workspaceevents'; /** * Google Chat adapter types. */ /** Service account credentials for JWT auth */ interface ServiceAccountCredentials$1 { client_email: string; private_key: string; project_id?: string; } /** Base config options shared by all auth methods */ interface GoogleChatAdapterBaseConfig { /** Override the Google Chat API root URL. Defaults to GOOGLE_CHAT_API_URL env var. */ apiUrl?: string; /** * Explicit opt-in to disable webhook signature verification. Required to * accept incoming webhooks when neither `googleChatProjectNumber` nor * `pubsubAudience` is configured. Without this flag set the constructor * throws — fail-closed by default. Only enable in development or when an * upstream layer (e.g. Cloud Run authenticated invocations) is providing * equivalent guarantees. */ disableSignatureVerification?: boolean; /** * HTTP endpoint URL for button click actions. * Required for HTTP endpoint apps - button clicks will be routed to this URL. * Should be the full URL of your webhook endpoint (e.g., "https://your-app.vercel.app/api/webhooks/gchat") */ endpointUrl?: string; /** * Google Cloud project number for verifying direct webhook JWTs. * When set, the adapter verifies the Bearer token on incoming Google Chat webhooks * by checking the JWT audience matches this project number. * Defaults to GOOGLE_CHAT_PROJECT_NUMBER env var. */ googleChatProjectNumber?: string; /** * User email to impersonate for Workspace Events API calls. * Required when using domain-wide delegation. * This user must have access to the Chat spaces you want to subscribe to. * Defaults to GOOGLE_CHAT_IMPERSONATE_USER env var. */ impersonateUser?: string; /** Logger instance for error reporting. Defaults to ConsoleLogger. */ logger?: Logger; /** * Expected audience for Pub/Sub push message JWT verification. * Typically the push endpoint URL configured in your Pub/Sub subscription. * When set, the adapter verifies the Authorization Bearer token on Pub/Sub messages. * Defaults to GOOGLE_CHAT_PUBSUB_AUDIENCE env var. */ pubsubAudience?: string; /** * Pub/Sub topic for receiving all messages via Workspace Events. * When set, the adapter will automatically create subscriptions when added to a space. * Format: "projects/my-project/topics/my-topic" * Defaults to GOOGLE_CHAT_PUBSUB_TOPIC env var. */ pubsubTopic?: string; /** Override bot username (optional) */ userName?: string; } /** Config using service account credentials (JSON key file) */ interface GoogleChatAdapterServiceAccountConfig extends GoogleChatAdapterBaseConfig { auth?: never; /** Service account credentials JSON. Defaults to GOOGLE_CHAT_CREDENTIALS env var (JSON). */ credentials: ServiceAccountCredentials$1; useApplicationDefaultCredentials?: never; } /** Config using Application Default Credentials (ADC) or Workload Identity Federation */ interface GoogleChatAdapterADCConfig extends GoogleChatAdapterBaseConfig { auth?: never; credentials?: never; /** * Use Application Default Credentials. * Works with: * - GOOGLE_APPLICATION_CREDENTIALS env var pointing to a JSON key file * - Workload Identity Federation (external_account JSON) * - GCE/Cloud Run/Cloud Functions default service account * - gcloud auth application-default login (local development) * Defaults to GOOGLE_CHAT_USE_ADC env var. */ useApplicationDefaultCredentials: true; } /** Config using a custom auth client */ interface GoogleChatAdapterCustomAuthConfig extends GoogleChatAdapterBaseConfig { /** Custom auth client (JWT, OAuth2, GoogleAuth, etc.) */ auth: Parameters[0]["auth"]; credentials?: never; useApplicationDefaultCredentials?: never; } /** Config with no auth fields - will auto-detect from env vars */ interface GoogleChatAdapterAutoConfig extends GoogleChatAdapterBaseConfig { auth?: never; credentials?: never; useApplicationDefaultCredentials?: never; } type GoogleChatAdapterConfig = GoogleChatAdapterServiceAccountConfig | GoogleChatAdapterADCConfig | GoogleChatAdapterCustomAuthConfig | GoogleChatAdapterAutoConfig; /** * Google Chat-specific format conversion using AST-based parsing. * * Google Chat supports a subset of text formatting: * - Bold: *text* * - Italic: _text_ * - Strikethrough: ~text~ * - Monospace: `text` * - Code blocks: ```text``` * - Links are auto-detected * * Very similar to Slack's mrkdwn format. */ declare class GoogleChatFormatConverter extends BaseFormatConverter { /** * Render an AST to Google Chat format. */ fromAst(ast: Root): string; /** * Parse Google Chat message into an AST. */ toAst(gchatText: string): Root; private nodeToGChat; } /** * Thread ID encoding/decoding utilities for Google Chat adapter. */ /** Google Chat-specific thread ID data */ interface GoogleChatThreadId { /** Whether this is a DM space */ isDM?: boolean; spaceName: string; threadName?: string; } /** * Google Workspace Events API integration for receiving all messages in a space. * * By default, Google Chat only sends webhooks for @mentions. To receive ALL messages * in a space, you need to create a Workspace Events subscription that publishes to * a Pub/Sub topic, which then pushes to your webhook endpoint. * * Setup flow: * 1. Create a Pub/Sub topic in your GCP project * 2. Create a Pub/Sub push subscription pointing to your /api/webhooks/gchat/pubsub endpoint * 3. Call createSpaceSubscription() to subscribe to message events for a space * 4. Handle Pub/Sub messages in your webhook with handlePubSubMessage() */ /** Options for creating a space subscription */ interface CreateSpaceSubscriptionOptions { /** The Pub/Sub topic to receive events (e.g., "projects/my-project/topics/my-topic") */ pubsubTopic: string; /** The space name (e.g., "spaces/AAAA...") */ spaceName: string; /** Optional TTL for the subscription in seconds (default: 1 day, max: 1 day for Chat) */ ttlSeconds?: number; } /** Result of creating a space subscription */ interface SpaceSubscriptionResult { /** When the subscription expires (ISO 8601) */ expireTime: string; /** The subscription resource name */ name: string; } /** Pub/Sub push message wrapper (what Google sends to your endpoint) */ interface PubSubPushMessage { message: { /** Base64 encoded event data */ data: string; messageId: string; publishTime: string; attributes?: Record; }; subscription: string; } /** Google Chat reaction data */ interface GoogleChatReaction { /** The emoji */ emoji?: { unicode?: string; }; /** Reaction resource name */ name: string; /** The user who added/removed the reaction */ user?: { name: string; displayName?: string; type?: string; }; } /** Decoded Workspace Events notification payload */ interface WorkspaceEventNotification { /** When the event occurred */ eventTime: string; /** Event type (e.g., "google.workspace.chat.message.v1.created") */ eventType: string; /** Present for message.created events */ message?: GoogleChatMessage; /** Present for reaction.created/deleted events */ reaction?: GoogleChatReaction; /** Space info */ space?: { name: string; type: string; }; /** The subscription that triggered this event */ subscription: string; /** The resource being watched (e.g., "//chat.googleapis.com/spaces/AAAA") */ targetResource: string; } /** Service account credentials for authentication */ interface ServiceAccountCredentials { client_email: string; private_key: string; project_id?: string; } /** Auth options - service account, ADC, or custom auth client */ type WorkspaceEventsAuthOptions = { credentials: ServiceAccountCredentials; impersonateUser?: string; } | { useApplicationDefaultCredentials: true; impersonateUser?: string; } | { auth: Parameters[0]["auth"]; }; /** * Create a Workspace Events subscription to receive all messages in a Chat space. * * Prerequisites: * - Enable the "Google Workspace Events API" in your GCP project * - Create a Pub/Sub topic and grant the Chat service account publish permissions * - The calling user/service account needs permission to access the space * * @example * ```typescript * const result = await createSpaceSubscription({ * spaceName: "spaces/AAAAxxxxxx", * pubsubTopic: "projects/my-project/topics/chat-events", * }, { * credentials: { * client_email: "...", * private_key: "...", * } * }); * ``` */ declare function createSpaceSubscription(options: CreateSpaceSubscriptionOptions, auth: WorkspaceEventsAuthOptions): Promise; /** * List active subscriptions for a target resource. */ declare function listSpaceSubscriptions(spaceName: string, auth: WorkspaceEventsAuthOptions): Promise>; /** * Delete a Workspace Events subscription. */ declare function deleteSpaceSubscription(subscriptionName: string, auth: WorkspaceEventsAuthOptions): Promise; /** * Decode a Pub/Sub push message into a Workspace Event notification. * * The message uses CloudEvents format where event metadata is in attributes * (ce-type, ce-source, ce-subject, ce-time) and the payload is base64 encoded. * * @example * ```typescript * // In your /api/webhooks/gchat/pubsub route: * const body = await request.json(); * const event = decodePubSubMessage(body); * * if (event.eventType === "google.workspace.chat.message.v1.created") { * // Handle new message * console.log("New message:", event.message?.text); * } * ``` */ declare function decodePubSubMessage(pushMessage: PubSubPushMessage): WorkspaceEventNotification; /** * Google Chat Card converter for cross-platform cards. * * Converts CardElement to Google Chat Card v2 format. * @see https://developers.google.com/chat/api/reference/rest/v1/cards */ interface GoogleChatCard { card: { header?: GoogleChatCardHeader; sections: GoogleChatCardSection[]; }; cardId?: string; } interface GoogleChatCardHeader { imageType?: "CIRCLE" | "SQUARE"; imageUrl?: string; subtitle?: string; title: string; } interface GoogleChatCardSection { collapsible?: boolean; header?: string; widgets: GoogleChatWidget[]; } interface GoogleChatWidget { buttonList?: { buttons: (GoogleChatButton | GoogleChatLinkButton)[]; }; decoratedText?: { topLabel?: string; text: string; bottomLabel?: string; startIcon?: { knownIcon?: string; }; }; divider?: Record; image?: { imageUrl: string; altText?: string; }; selectionInput?: GoogleChatSelectionInput; textParagraph?: { text: string; }; } interface GoogleChatSelectionInput { items: GoogleChatSelectionItem[]; label: string; name: string; onChangeAction: { function: string; parameters: Array<{ key: string; value: string; }>; }; type: "DROPDOWN" | "RADIO_BUTTON"; } interface GoogleChatSelectionItem { selected?: boolean; text: string; value: string; } interface GoogleChatButton { color?: { red: number; green: number; blue: number; }; disabled?: boolean; onClick: { action: { function: string; parameters: Array<{ key: string; value: string; }>; }; }; text: string; } interface GoogleChatLinkButton { color?: { red: number; green: number; blue: number; }; onClick: { openLink: { url: string; }; }; text: string; } /** * Options for card conversion. */ interface CardConversionOptions { /** Unique card ID for interactive cards */ cardId?: string; /** * HTTP endpoint URL for button actions. * Required for HTTP endpoint apps - button clicks will be routed to this URL. */ endpointUrl?: string; } /** * Convert a CardElement to Google Chat Card v2 format. */ declare function cardToGoogleCard(card: CardElement, options?: CardConversionOptions | string): GoogleChatCard; /** * Generate fallback text from a card element. * Used when cards aren't supported. */ declare function cardToFallbackText(card: CardElement): string; /** Google Chat message structure */ interface GoogleChatMessage { annotations?: Array<{ type: string; startIndex?: number; length?: number; userMention?: { user: { name: string; displayName?: string; type: string; }; type: string; }; }>; argumentText?: string; attachment?: Array<{ name: string; contentName: string; contentType: string; downloadUri?: string; attachmentDataRef?: { resourceName?: string | null; } | null; }>; createTime: string; formattedText?: string; name: string; sender: { avatarUrl?: string; name: string; displayName: string; type: string; email?: string; }; space?: { name: string; type: string; displayName?: string; }; text: string; thread?: { name: string; }; } /** Google Chat space structure */ interface GoogleChatSpace { displayName?: string; name: string; /** Whether this is a single-user DM with the bot */ singleUserBotDm?: boolean; spaceThreadingState?: string; /** Space type in newer API format: "SPACE", "GROUP_CHAT", "DIRECT_MESSAGE" */ spaceType?: string; type: string; } /** Google Chat user structure */ interface GoogleChatUser { displayName: string; email?: string; name: string; type: string; } interface GoogleChatFormInput { stringInputs?: { value?: string[]; }; } type GoogleChatFormInputs = Record; /** * Google Workspace Add-ons event format. * This is the format used when configuring the app via Google Cloud Console. */ interface GoogleChatEvent { chat?: { user?: GoogleChatUser; eventTime?: string; messagePayload?: { space: GoogleChatSpace; message: GoogleChatMessage; }; /** Present when the bot is added to a space */ addedToSpacePayload?: { space: GoogleChatSpace; }; /** Present when the bot is removed from a space */ removedFromSpacePayload?: { space: GoogleChatSpace; }; /** Present when a card button is clicked */ buttonClickedPayload?: { space: GoogleChatSpace; message: GoogleChatMessage; user: GoogleChatUser; }; }; commonEventObject?: { formInputs?: GoogleChatFormInputs; userLocale?: string; hostApp?: string; platform?: string; /** The function name invoked (for card clicks) */ invokedFunction?: string; /** Parameters passed to the function */ parameters?: Record; }; } /** Cached subscription info */ interface SpaceSubscriptionInfo { expireTime: number; subscriptionName: string; } declare class GoogleChatAdapter implements Adapter { readonly name = "gchat"; readonly userName: string; /** Bot's user ID (e.g., "users/123...") - learned from annotations */ botUserId?: string; protected readonly chatApi: chat_v1.Chat; protected chat: ChatInstance | null; protected state: StateAdapter | null; protected readonly logger: Logger; protected readonly formatConverter: GoogleChatFormatConverter; protected readonly pubsubTopic?: string; protected readonly credentials?: ServiceAccountCredentials$1; protected readonly useADC: boolean; /** Custom auth client (e.g., Vercel OIDC) */ protected readonly customAuth?: Parameters[0]["auth"]; /** Auth client for making authenticated requests */ protected readonly authClient: Parameters[0]["auth"]; /** User email to impersonate for Workspace Events API (domain-wide delegation) */ protected readonly impersonateUser?: string; /** In-progress subscription creations to prevent duplicate requests */ private readonly pendingSubscriptions; /** Chat API client with impersonation for user-context operations (DMs, etc.) */ protected readonly impersonatedChatApi?: chat_v1.Chat; /** HTTP endpoint URL for button click actions */ protected endpointUrl?: string; /** Google Cloud project number for verifying direct webhook JWTs */ protected readonly googleChatProjectNumber?: string; /** Expected audience for Pub/Sub push message JWT verification */ protected readonly pubsubAudience?: string; /** Explicit opt-in to skip JWT verification (fail-open). */ protected readonly disableSignatureVerification: boolean; /** OAuth2 client for verifying Google-signed JWTs */ private readonly oauth2Client; /** Track whether we've already warned about missing verification config */ private warnedNoWebhookVerification; private warnedNoPubsubVerification; /** User info cache for display name lookups - initialized later in initialize() */ private userInfoCache; constructor(config?: GoogleChatAdapterConfig); initialize(chat: ChatInstance): Promise; /** * Called when a thread is subscribed to. * Ensures the space has a Workspace Events subscription so we receive all messages. */ onThreadSubscribe(threadId: string): Promise; /** * Ensure a Workspace Events subscription exists for a space. * Creates one if it doesn't exist or is about to expire. */ protected ensureSpaceSubscription(spaceName: string): Promise; /** * Create a Workspace Events subscription and cache the result. */ protected createSpaceSubscriptionWithCache(spaceName: string, cacheKey: string): Promise; /** * Check if a subscription already exists for this space. */ protected findExistingSubscription(spaceName: string, authOptions: WorkspaceEventsAuthOptions): Promise; /** * Get auth options for Workspace Events API calls. */ protected getAuthOptions(): WorkspaceEventsAuthOptions | null; /** * Verify a Google-signed JWT Bearer token from the Authorization header. * Used for both direct Google Chat webhooks and Pub/Sub push messages. * * @param request - The incoming HTTP request * @param expectedAudience - The expected audience claim in the JWT * @returns true if verification succeeds or is not configured */ protected verifyBearerToken(request: Request, expectedAudience: string): Promise; getUser(userId: string): Promise; handleWebhook(request: Request, options?: WebhookOptions): Promise; /** * Handle Pub/Sub push messages from Workspace Events subscriptions. * These contain all messages in a space, not just @mentions. */ protected handlePubSubMessage(pushMessage: PubSubPushMessage, options?: WebhookOptions): Response; /** * Handle message events received via Pub/Sub (Workspace Events). */ protected handlePubSubMessageEvent(notification: WorkspaceEventNotification, options?: WebhookOptions): void; /** * Handle reaction events received via Pub/Sub (Workspace Events). * Fetches the message to get thread context for proper reply threading. */ protected handlePubSubReactionEvent(notification: WorkspaceEventNotification, options?: WebhookOptions): void; /** * Parse a Pub/Sub message into the standard Message format. * Resolves user display names from cache since Pub/Sub messages don't include them. */ protected parsePubSubMessage(notification: WorkspaceEventNotification, threadId: string): Promise>; /** * Handle bot being added to a space - create Workspace Events subscription. */ protected handleAddedToSpace(space: GoogleChatSpace, options?: WebhookOptions): void; /** * Handle card button clicks. * For HTTP endpoint apps, the actionId is passed via parameters (since function is the URL). * For other deployments, actionId may be in invokedFunction. */ protected handleCardClick(event: GoogleChatEvent, options?: WebhookOptions): void; protected getFormInputValue(formInputs: GoogleChatFormInputs | undefined, actionId: string): string | undefined; /** * Handle direct webhook message events (Add-ons format). */ protected handleMessageEvent(event: GoogleChatEvent, options?: WebhookOptions): void; protected parseGoogleChatMessage(event: GoogleChatEvent, threadId: string): Message; postMessage(threadId: string, message: AdapterPostableMessage): Promise>; postEphemeral(threadId: string, userId: string, message: AdapterPostableMessage): Promise; /** * Create an Attachment object from a Google Chat attachment. */ protected createAttachment(att: { contentType?: string | null; downloadUri?: string | null; contentName?: string | null; thumbnailUri?: string | null; attachmentDataRef?: { resourceName?: string | null; } | null; }): Attachment; protected fetchAttachmentData(resourceName?: string, url?: string): Promise; rehydrateAttachment(attachment: Attachment): Attachment; editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise>; deleteMessage(_threadId: string, messageId: string): Promise; addReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise; removeReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise; startTyping(_threadId: string, _status?: string): Promise; /** * Open a direct message conversation with a user. * Returns a thread ID that can be used to post messages. * * For Google Chat, this first tries to find an existing DM space with the user. * If no DM exists, it creates one using spaces.setup. * * @param userId - The user's resource name (e.g., "users/123456") */ openDM(userId: string): Promise; fetchMessages(threadId: string, options?: FetchOptions): Promise>; /** * Fetch messages in backward direction (most recent first). * GChat API defaults to createTime ASC (oldest first), so we request DESC * to get the most recent messages, then reverse for chronological order within page. */ protected fetchMessagesBackward(api: chat_v1.Chat, spaceName: string, threadId: string, filter: string | undefined, limit: number, cursor?: string): Promise>; /** * Fetch messages in forward direction (oldest first). * * GChat API defaults to createTime ASC (oldest first), which is what we want. * For forward pagination, we: * 1. If no cursor: Fetch ALL messages (already in chronological order) * 2. If cursor: Cursor is a message name, skip to after that message * * Note: This is less efficient than backward for large message histories, * as it requires fetching all messages to find the cursor position. */ protected fetchMessagesForward(api: chat_v1.Chat, spaceName: string, threadId: string, filter: string | undefined, limit: number, cursor?: string): Promise>; /** * Parse a message from the list API into the standard Message format. * Resolves user display names and properly determines isMe. */ protected parseGChatListMessage(msg: chat_v1.Schema$Message, spaceName: string, _threadId: string): Promise>; fetchThread(threadId: string): Promise; /** * Derive channel ID from a Google Chat thread ID. * gchat:{spaceName}:{encodedThreadName} -> gchat:{spaceName} */ channelIdFromThreadId(threadId: string): string; /** * Fetch channel-level messages (top-level posts only, not thread replies). * * Google Chat doesn't support server-side filtering for thread roots, * so we fetch messages and filter client-side. A message is a thread root * when the message ID prefix (before the dot) matches its thread ID. * For example: message "spaces/X/messages/ABC.ABC" in thread "spaces/X/threads/ABC" * is a root, while "spaces/X/messages/ABC.DEF" is a reply. */ fetchChannelMessages(channelId: string, options?: FetchOptions): Promise>; /** * Check if a GChat message is a thread root (not a reply). * * Thread root messages have a name like "spaces/X/messages/THREAD_ID.THREAD_ID" * where both parts of the dotted message ID match. Thread replies have * "spaces/X/messages/THREAD_ID.DIFFERENT_ID". * * Messages without a thread field (e.g., in non-threaded spaces) are always top-level. */ protected isThreadRoot(msg: chat_v1.Schema$Message): boolean; /** * Fetch channel messages backward (most recent first), filtered to thread roots only. * Over-fetches and filters client-side since the API doesn't support this filter. */ protected fetchChannelMessagesBackward(api: chat_v1.Chat, spaceName: string, channelId: string, limit: number, cursor?: string): Promise>; /** * Fetch channel messages forward (oldest first), filtered to thread roots only. */ protected fetchChannelMessagesForward(api: chat_v1.Chat, spaceName: string, channelId: string, limit: number, cursor?: string): Promise>; /** * List threads in a Google Chat space. * Fetches messages and deduplicates by thread name. */ listThreads(channelId: string, options?: ListThreadsOptions): Promise>; /** * Fetch Google Chat space info/metadata. */ fetchChannelInfo(channelId: string): Promise; /** * Post a message to a space top-level (starts a new conversation, not in a thread). */ postChannelMessage(channelId: string, message: AdapterPostableMessage): Promise>; encodeThreadId(platformData: GoogleChatThreadId): string; /** * Check if a thread is a direct message conversation. */ isDM(threadId: string): boolean; decodeThreadId(threadId: string): GoogleChatThreadId; parseMessage(raw: unknown): Message; renderFormatted(content: FormattedContent): string; /** * Normalize bot mentions in message text. * Google Chat uses the bot's display name (e.g., "@Chat SDK Demo") but the * Chat SDK expects "@{userName}" format. This method replaces bot mentions * with the adapter's userName so mention detection works properly. * Also learns the bot's user ID from annotations for isMe detection. */ protected normalizeBotMentions(message: GoogleChatMessage): string; /** * Check if a message is from this bot. * * Bot user ID is learned dynamically from message annotations when the bot * is @mentioned. Until we learn the ID, we cannot reliably determine isMe. * * This is safer than the previous approach of assuming all BOT messages are * from self, which would incorrectly filter messages from other bots in * multi-bot spaces (especially via Pub/Sub). */ protected isMessageFromSelf(message: GoogleChatMessage): boolean; protected handleGoogleChatError(error: unknown, context?: string): never; } declare function createGoogleChatAdapter(config?: GoogleChatAdapterConfig): GoogleChatAdapter; export { type CreateSpaceSubscriptionOptions, GoogleChatAdapter, type GoogleChatAdapterADCConfig, type GoogleChatAdapterAutoConfig, type GoogleChatAdapterBaseConfig, type GoogleChatAdapterConfig, type GoogleChatAdapterCustomAuthConfig, type GoogleChatAdapterServiceAccountConfig, type GoogleChatEvent, GoogleChatFormatConverter, type GoogleChatMessage, type GoogleChatSpace, type GoogleChatThreadId, type GoogleChatUser, type PubSubPushMessage, type ServiceAccountCredentials$1 as ServiceAccountCredentials, type SpaceSubscriptionResult, type WorkspaceEventNotification, type WorkspaceEventsAuthOptions, cardToFallbackText, cardToGoogleCard, createGoogleChatAdapter, createSpaceSubscription, decodePubSubMessage, deleteSpaceSubscription, listSpaceSubscriptions };