@chat-adapter/gchat 4.0.2 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CardElement, BaseFormatConverter, Root, Adapter, ChatInstance, WebhookOptions, PostableMessage, RawMessage, EmojiValue, FetchOptions, Message, ThreadInfo, FormattedContent } from 'chat';
1
+ import { CardElement, BaseFormatConverter, Root, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage, RawMessage, EmojiValue, FetchOptions, Message, ThreadInfo, FormattedContent } from 'chat';
2
2
  import { google } from 'googleapis';
3
3
 
4
4
  /**
@@ -510,20 +510,20 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
510
510
  */
511
511
  private handleMessageEvent;
512
512
  private parseGoogleChatMessage;
513
- postMessage(threadId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
513
+ postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
514
514
  /**
515
- * Extract card element from a PostableMessage if present.
515
+ * Extract card element from a message if present.
516
516
  */
517
517
  private extractCard;
518
518
  /**
519
- * Extract files from a PostableMessage if present.
519
+ * Extract files from a message if present.
520
520
  */
521
521
  private extractFiles;
522
522
  /**
523
523
  * Create an Attachment object from a Google Chat attachment.
524
524
  */
525
525
  private createAttachment;
526
- editMessage(threadId: string, messageId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
526
+ editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
527
527
  deleteMessage(_threadId: string, messageId: string): Promise<void>;
528
528
  addReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
529
529
  removeReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
package/dist/index.js CHANGED
@@ -444,6 +444,7 @@ var GoogleChatAdapter = class {
444
444
  let auth;
445
445
  const scopes = [
446
446
  "https://www.googleapis.com/auth/chat.bot",
447
+ "https://www.googleapis.com/auth/chat.messages.readonly",
447
448
  "https://www.googleapis.com/auth/chat.messages.reactions.create",
448
449
  "https://www.googleapis.com/auth/chat.messages.reactions",
449
450
  "https://www.googleapis.com/auth/chat.spaces.create"
@@ -477,7 +478,8 @@ var GoogleChatAdapter = class {
477
478
  key: this.credentials.private_key,
478
479
  scopes: [
479
480
  "https://www.googleapis.com/auth/chat.spaces",
480
- "https://www.googleapis.com/auth/chat.spaces.create"
481
+ "https://www.googleapis.com/auth/chat.spaces.create",
482
+ "https://www.googleapis.com/auth/chat.messages.readonly"
481
483
  ],
482
484
  subject: this.impersonateUser
483
485
  });
@@ -489,7 +491,8 @@ var GoogleChatAdapter = class {
489
491
  const impersonatedAuth = new google2.auth.GoogleAuth({
490
492
  scopes: [
491
493
  "https://www.googleapis.com/auth/chat.spaces",
492
- "https://www.googleapis.com/auth/chat.spaces.create"
494
+ "https://www.googleapis.com/auth/chat.spaces.create",
495
+ "https://www.googleapis.com/auth/chat.messages.readonly"
493
496
  ],
494
497
  clientOptions: {
495
498
  subject: this.impersonateUser
@@ -1166,7 +1169,7 @@ var GoogleChatAdapter = class {
1166
1169
  }
1167
1170
  }
1168
1171
  /**
1169
- * Extract card element from a PostableMessage if present.
1172
+ * Extract card element from a message if present.
1170
1173
  */
1171
1174
  extractCard(message) {
1172
1175
  if (isCardElement(message)) {
@@ -1178,7 +1181,7 @@ var GoogleChatAdapter = class {
1178
1181
  return null;
1179
1182
  }
1180
1183
  /**
1181
- * Extract files from a PostableMessage if present.
1184
+ * Extract files from a message if present.
1182
1185
  */
1183
1186
  extractFiles(message) {
1184
1187
  if (typeof message === "object" && message !== null && "files" in message) {
@@ -1432,12 +1435,14 @@ var GoogleChatAdapter = class {
1432
1435
  }
1433
1436
  async fetchMessages(threadId, options = {}) {
1434
1437
  const { spaceName } = this.decodeThreadId(threadId);
1438
+ const api = this.impersonatedChatApi || this.chatApi;
1435
1439
  try {
1436
1440
  this.logger?.debug("GChat API: spaces.messages.list", {
1437
1441
  spaceName,
1438
- pageSize: options.limit || 100
1442
+ pageSize: options.limit || 100,
1443
+ impersonated: !!this.impersonatedChatApi
1439
1444
  });
1440
- const response = await this.chatApi.spaces.messages.list({
1445
+ const response = await api.spaces.messages.list({
1441
1446
  parent: spaceName,
1442
1447
  pageSize: options.limit || 100,
1443
1448
  pageToken: options.before
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts","../src/workspace-events.ts"],"sourcesContent":["import type {\n ActionEvent,\n Adapter,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n PostableMessage,\n RawMessage,\n ReactionEvent,\n StateAdapter,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n isCardElement,\n RateLimitError,\n} from \"chat\";\nimport { type chat_v1, google } from \"googleapis\";\nimport { cardToGoogleCard } from \"./cards\";\nimport { GoogleChatFormatConverter } from \"./markdown\";\nimport {\n createSpaceSubscription,\n decodePubSubMessage,\n listSpaceSubscriptions,\n type PubSubPushMessage,\n type WorkspaceEventNotification,\n type WorkspaceEventsAuthOptions,\n} from \"./workspace-events\";\n\n/** How long before expiry to refresh subscriptions (1 hour) */\nconst SUBSCRIPTION_REFRESH_BUFFER_MS = 60 * 60 * 1000;\n/** TTL for subscription cache entries (25 hours - longer than max subscription lifetime) */\nconst SUBSCRIPTION_CACHE_TTL_MS = 25 * 60 * 60 * 1000;\n/** Key prefix for space subscription cache */\nconst SPACE_SUB_KEY_PREFIX = \"gchat:space-sub:\";\n/** Key prefix for user info cache */\nconst USER_INFO_KEY_PREFIX = \"gchat:user:\";\n/** TTL for user info cache (7 days) */\nconst USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** Cached user info */\ninterface CachedUserInfo {\n displayName: string;\n email?: string;\n}\n\n/** Service account credentials for JWT auth */\nexport interface ServiceAccountCredentials {\n client_email: string;\n private_key: string;\n project_id?: string;\n}\n\n/** Base config options shared by all auth methods */\nexport interface GoogleChatAdapterBaseConfig {\n /** Override bot username (optional) */\n userName?: string;\n /**\n * Pub/Sub topic for receiving all messages via Workspace Events.\n * When set, the adapter will automatically create subscriptions when added to a space.\n * Format: \"projects/my-project/topics/my-topic\"\n */\n pubsubTopic?: string;\n /**\n * User email to impersonate for Workspace Events API calls.\n * Required when using domain-wide delegation.\n * This user must have access to the Chat spaces you want to subscribe to.\n */\n impersonateUser?: string;\n /**\n * HTTP endpoint URL for button click actions.\n * Required for HTTP endpoint apps - button clicks will be routed to this URL.\n * Should be the full URL of your webhook endpoint (e.g., \"https://your-app.vercel.app/api/webhooks/gchat\")\n */\n endpointUrl?: string;\n}\n\n/** Config using service account credentials (JSON key file) */\nexport interface GoogleChatAdapterServiceAccountConfig\n extends GoogleChatAdapterBaseConfig {\n /** Service account credentials JSON */\n credentials: ServiceAccountCredentials;\n auth?: never;\n useApplicationDefaultCredentials?: never;\n}\n\n/** Config using Application Default Credentials (ADC) or Workload Identity Federation */\nexport interface GoogleChatAdapterADCConfig\n extends GoogleChatAdapterBaseConfig {\n /**\n * Use Application Default Credentials.\n * Works with:\n * - GOOGLE_APPLICATION_CREDENTIALS env var pointing to a JSON key file\n * - Workload Identity Federation (external_account JSON)\n * - GCE/Cloud Run/Cloud Functions default service account\n * - gcloud auth application-default login (local development)\n */\n useApplicationDefaultCredentials: true;\n credentials?: never;\n auth?: never;\n}\n\n/** Config using a custom auth client */\nexport interface GoogleChatAdapterCustomAuthConfig\n extends GoogleChatAdapterBaseConfig {\n /** Custom auth client (JWT, OAuth2, GoogleAuth, etc.) */\n auth: Parameters<typeof google.chat>[0][\"auth\"];\n credentials?: never;\n useApplicationDefaultCredentials?: never;\n}\n\nexport type GoogleChatAdapterConfig =\n | GoogleChatAdapterServiceAccountConfig\n | GoogleChatAdapterADCConfig\n | GoogleChatAdapterCustomAuthConfig;\n\n/** Google Chat-specific thread ID data */\nexport interface GoogleChatThreadId {\n spaceName: string;\n threadName?: string;\n /** Whether this is a DM space */\n isDM?: boolean;\n}\n\n/** Google Chat message structure */\nexport interface GoogleChatMessage {\n name: string;\n sender: {\n name: string;\n displayName: string;\n type: string;\n email?: string;\n };\n text: string;\n argumentText?: string;\n formattedText?: string;\n thread?: {\n name: string;\n };\n space?: {\n name: string;\n type: string;\n displayName?: string;\n };\n createTime: string;\n annotations?: Array<{\n type: string;\n startIndex?: number;\n length?: number;\n userMention?: {\n user: { name: string; displayName?: string; type: string };\n type: string;\n };\n }>;\n attachment?: Array<{\n name: string;\n contentName: string;\n contentType: string;\n downloadUri?: string;\n }>;\n}\n\n/** Google Chat space structure */\nexport interface GoogleChatSpace {\n name: string;\n type: string;\n displayName?: string;\n spaceThreadingState?: string;\n /** Space type in newer API format: \"SPACE\", \"GROUP_CHAT\", \"DIRECT_MESSAGE\" */\n spaceType?: string;\n /** Whether this is a single-user DM with the bot */\n singleUserBotDm?: boolean;\n}\n\n/** Google Chat user structure */\nexport interface GoogleChatUser {\n name: string;\n displayName: string;\n type: string;\n email?: string;\n}\n\n/**\n * Google Workspace Add-ons event format.\n * This is the format used when configuring the app via Google Cloud Console.\n */\nexport interface GoogleChatEvent {\n commonEventObject?: {\n userLocale?: string;\n hostApp?: string;\n platform?: string;\n /** The function name invoked (for card clicks) */\n invokedFunction?: string;\n /** Parameters passed to the function */\n parameters?: Record<string, string>;\n };\n chat?: {\n user?: GoogleChatUser;\n eventTime?: string;\n messagePayload?: {\n space: GoogleChatSpace;\n message: GoogleChatMessage;\n };\n /** Present when the bot is added to a space */\n addedToSpacePayload?: {\n space: GoogleChatSpace;\n };\n /** Present when the bot is removed from a space */\n removedFromSpacePayload?: {\n space: GoogleChatSpace;\n };\n /** Present when a card button is clicked */\n buttonClickedPayload?: {\n space: GoogleChatSpace;\n message: GoogleChatMessage;\n user: GoogleChatUser;\n };\n };\n}\n\n/** Cached subscription info */\ninterface SpaceSubscriptionInfo {\n subscriptionName: string;\n expireTime: number; // Unix timestamp ms\n}\n\nexport class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown> {\n readonly name = \"gchat\";\n readonly userName: string;\n /** Bot's user ID (e.g., \"users/123...\") - learned from annotations */\n botUserId?: string;\n\n private chatApi: chat_v1.Chat;\n private chat: ChatInstance | null = null;\n private state: StateAdapter | null = null;\n private logger: Logger | null = null;\n private formatConverter = new GoogleChatFormatConverter();\n private pubsubTopic?: string;\n private credentials?: ServiceAccountCredentials;\n private useADC = false;\n /** Custom auth client (e.g., Vercel OIDC) */\n private customAuth?: Parameters<typeof google.chat>[0][\"auth\"];\n /** Auth client for making authenticated requests */\n private authClient!: Parameters<typeof google.chat>[0][\"auth\"];\n /** User email to impersonate for Workspace Events API (domain-wide delegation) */\n private impersonateUser?: string;\n /** In-progress subscription creations to prevent duplicate requests */\n private pendingSubscriptions = new Map<string, Promise<void>>();\n /** Chat API client with impersonation for user-context operations (DMs, etc.) */\n private impersonatedChatApi?: chat_v1.Chat;\n /** HTTP endpoint URL for button click actions */\n private endpointUrl?: string;\n\n constructor(config: GoogleChatAdapterConfig) {\n this.userName = config.userName || \"bot\";\n this.pubsubTopic = config.pubsubTopic;\n this.impersonateUser = config.impersonateUser;\n this.endpointUrl = config.endpointUrl;\n\n let auth: Parameters<typeof google.chat>[0][\"auth\"];\n\n // Scopes needed for full bot functionality including reactions and DMs\n // Note: chat.spaces.create requires domain-wide delegation to work\n const scopes = [\n \"https://www.googleapis.com/auth/chat.bot\",\n \"https://www.googleapis.com/auth/chat.messages.reactions.create\",\n \"https://www.googleapis.com/auth/chat.messages.reactions\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n ];\n\n if (\"credentials\" in config && config.credentials) {\n // Service account credentials (JWT)\n this.credentials = config.credentials;\n auth = new google.auth.JWT({\n email: config.credentials.client_email,\n key: config.credentials.private_key,\n scopes,\n });\n } else if (\n \"useApplicationDefaultCredentials\" in config &&\n config.useApplicationDefaultCredentials\n ) {\n // Application Default Credentials (ADC)\n // Works with Workload Identity Federation, GCE metadata, GOOGLE_APPLICATION_CREDENTIALS env var\n this.useADC = true;\n auth = new google.auth.GoogleAuth({\n scopes,\n });\n } else if (\"auth\" in config && config.auth) {\n // Custom auth client provided directly (e.g., Vercel OIDC)\n this.customAuth = config.auth;\n auth = config.auth;\n } else {\n throw new Error(\n \"GoogleChatAdapter requires one of: credentials, useApplicationDefaultCredentials, or auth\",\n );\n }\n\n this.authClient = auth;\n this.chatApi = google.chat({ version: \"v1\", auth });\n\n // Create impersonated Chat API for user-context operations (DMs)\n // Domain-wide delegation requires setting the `subject` claim to the impersonated user\n if (this.impersonateUser) {\n if (this.credentials) {\n const impersonatedAuth = new google.auth.JWT({\n email: this.credentials.client_email,\n key: this.credentials.private_key,\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n ],\n subject: this.impersonateUser,\n });\n this.impersonatedChatApi = google.chat({\n version: \"v1\",\n auth: impersonatedAuth,\n });\n } else if (this.useADC) {\n // ADC with impersonation (requires clientOptions.subject support)\n const impersonatedAuth = new google.auth.GoogleAuth({\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n ],\n clientOptions: {\n subject: this.impersonateUser,\n },\n });\n this.impersonatedChatApi = google.chat({\n version: \"v1\",\n auth: impersonatedAuth,\n });\n }\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.state = chat.getState();\n this.logger = chat.getLogger(this.name);\n\n // Restore persisted bot user ID from state (for serverless environments)\n if (!this.botUserId) {\n const savedBotUserId = await this.state.get<string>(\"gchat:botUserId\");\n if (savedBotUserId) {\n this.botUserId = savedBotUserId;\n this.logger?.debug(\"Restored bot user ID from state\", {\n botUserId: this.botUserId,\n });\n }\n }\n }\n\n /**\n * Called when a thread is subscribed to.\n * Ensures the space has a Workspace Events subscription so we receive all messages.\n */\n async onThreadSubscribe(threadId: string): Promise<void> {\n this.logger?.info(\"onThreadSubscribe called\", {\n threadId,\n hasPubsubTopic: !!this.pubsubTopic,\n pubsubTopic: this.pubsubTopic,\n });\n\n if (!this.pubsubTopic) {\n this.logger?.warn(\n \"No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var.\",\n );\n return;\n }\n\n const { spaceName } = this.decodeThreadId(threadId);\n await this.ensureSpaceSubscription(spaceName);\n }\n\n /**\n * Ensure a Workspace Events subscription exists for a space.\n * Creates one if it doesn't exist or is about to expire.\n */\n private async ensureSpaceSubscription(spaceName: string): Promise<void> {\n this.logger?.info(\"ensureSpaceSubscription called\", {\n spaceName,\n hasPubsubTopic: !!this.pubsubTopic,\n hasState: !!this.state,\n hasCredentials: !!this.credentials,\n hasADC: this.useADC,\n });\n\n if (!this.pubsubTopic || !this.state) {\n this.logger?.warn(\"ensureSpaceSubscription skipped - missing config\", {\n hasPubsubTopic: !!this.pubsubTopic,\n hasState: !!this.state,\n });\n return;\n }\n\n const cacheKey = `${SPACE_SUB_KEY_PREFIX}${spaceName}`;\n\n // Check if we already have a valid subscription\n const cached = await this.state.get<SpaceSubscriptionInfo>(cacheKey);\n if (cached) {\n const timeUntilExpiry = cached.expireTime - Date.now();\n if (timeUntilExpiry > SUBSCRIPTION_REFRESH_BUFFER_MS) {\n this.logger?.debug(\"Space subscription still valid\", {\n spaceName,\n expiresIn: Math.round(timeUntilExpiry / 1000 / 60),\n });\n return;\n }\n this.logger?.debug(\"Space subscription expiring soon, will refresh\", {\n spaceName,\n expiresIn: Math.round(timeUntilExpiry / 1000 / 60),\n });\n }\n\n // Check if we're already creating a subscription for this space\n const pending = this.pendingSubscriptions.get(spaceName);\n if (pending) {\n this.logger?.debug(\"Subscription creation already in progress\", {\n spaceName,\n });\n return pending;\n }\n\n // Create the subscription\n const createPromise = this.createSpaceSubscriptionWithCache(\n spaceName,\n cacheKey,\n );\n this.pendingSubscriptions.set(spaceName, createPromise);\n\n try {\n await createPromise;\n } finally {\n this.pendingSubscriptions.delete(spaceName);\n }\n }\n\n /**\n * Create a Workspace Events subscription and cache the result.\n */\n private async createSpaceSubscriptionWithCache(\n spaceName: string,\n cacheKey: string,\n ): Promise<void> {\n const authOptions = this.getAuthOptions();\n this.logger?.info(\"createSpaceSubscriptionWithCache\", {\n spaceName,\n hasAuthOptions: !!authOptions,\n hasCredentials: !!this.credentials,\n hasADC: this.useADC,\n });\n\n if (!authOptions) {\n this.logger?.error(\n \"Cannot create subscription: no auth configured. Use GOOGLE_CHAT_CREDENTIALS, GOOGLE_CHAT_USE_ADC=true, or custom auth.\",\n );\n return;\n }\n\n const pubsubTopic = this.pubsubTopic;\n if (!pubsubTopic) return;\n\n try {\n // First check if a subscription already exists via the API\n const existing = await this.findExistingSubscription(\n spaceName,\n authOptions,\n );\n if (existing) {\n this.logger?.debug(\"Found existing subscription\", {\n spaceName,\n subscriptionName: existing.subscriptionName,\n });\n // Cache it\n if (this.state) {\n await this.state.set<SpaceSubscriptionInfo>(\n cacheKey,\n existing,\n SUBSCRIPTION_CACHE_TTL_MS,\n );\n }\n return;\n }\n\n this.logger?.info(\"Creating Workspace Events subscription\", {\n spaceName,\n pubsubTopic,\n });\n\n const result = await createSpaceSubscription(\n { spaceName, pubsubTopic },\n authOptions,\n );\n\n const subscriptionInfo: SpaceSubscriptionInfo = {\n subscriptionName: result.name,\n expireTime: new Date(result.expireTime).getTime(),\n };\n\n // Cache the subscription info\n if (this.state) {\n await this.state.set<SpaceSubscriptionInfo>(\n cacheKey,\n subscriptionInfo,\n SUBSCRIPTION_CACHE_TTL_MS,\n );\n }\n\n this.logger?.info(\"Workspace Events subscription created\", {\n spaceName,\n subscriptionName: result.name,\n expireTime: result.expireTime,\n });\n } catch (error) {\n this.logger?.error(\"Failed to create Workspace Events subscription\", {\n spaceName,\n error,\n });\n // Don't throw - subscription failure shouldn't break the main flow\n }\n }\n\n /**\n * Check if a subscription already exists for this space.\n */\n private async findExistingSubscription(\n spaceName: string,\n authOptions: WorkspaceEventsAuthOptions,\n ): Promise<SpaceSubscriptionInfo | null> {\n try {\n const subscriptions = await listSpaceSubscriptions(\n spaceName,\n authOptions,\n );\n for (const sub of subscriptions) {\n // Check if this subscription is still valid\n const expireTime = new Date(sub.expireTime).getTime();\n if (expireTime > Date.now() + SUBSCRIPTION_REFRESH_BUFFER_MS) {\n return {\n subscriptionName: sub.name,\n expireTime,\n };\n }\n }\n } catch (error) {\n this.logger?.debug(\"Error checking existing subscriptions\", { error });\n }\n return null;\n }\n\n /**\n * Get auth options for Workspace Events API calls.\n */\n private getAuthOptions(): WorkspaceEventsAuthOptions | null {\n if (this.credentials) {\n return {\n credentials: this.credentials,\n impersonateUser: this.impersonateUser,\n };\n }\n if (this.useADC) {\n return {\n useApplicationDefaultCredentials: true as const,\n impersonateUser: this.impersonateUser,\n };\n }\n if (this.customAuth) {\n return { auth: this.customAuth };\n }\n return null;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n // Auto-detect endpoint URL from incoming request for button click routing\n // This allows HTTP endpoint apps to work without manual endpointUrl configuration\n if (!this.endpointUrl) {\n try {\n const url = new URL(request.url);\n // Preserve the full URL including query strings\n this.endpointUrl = url.toString();\n this.logger?.debug(\"Auto-detected endpoint URL\", {\n endpointUrl: this.endpointUrl,\n });\n } catch {\n // URL parsing failed, endpointUrl will remain undefined\n }\n }\n\n const body = await request.text();\n this.logger?.debug(\"GChat webhook raw body\", { body });\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Check if this is a Pub/Sub push message (from Workspace Events subscription)\n const maybePubSub = parsed as PubSubPushMessage;\n if (maybePubSub.message?.data && maybePubSub.subscription) {\n return this.handlePubSubMessage(maybePubSub, options);\n }\n\n // Otherwise, treat as a direct Google Chat webhook event\n const event = parsed as GoogleChatEvent;\n\n // Handle ADDED_TO_SPACE - automatically create subscription\n const addedPayload = event.chat?.addedToSpacePayload;\n if (addedPayload) {\n this.logger?.debug(\"Bot added to space\", {\n space: addedPayload.space.name,\n spaceType: addedPayload.space.type,\n });\n this.handleAddedToSpace(addedPayload.space, options);\n }\n\n // Handle REMOVED_FROM_SPACE (for logging)\n const removedPayload = event.chat?.removedFromSpacePayload;\n if (removedPayload) {\n this.logger?.debug(\"Bot removed from space\", {\n space: removedPayload.space.name,\n });\n }\n\n // Handle card button clicks\n const buttonClickedPayload = event.chat?.buttonClickedPayload;\n const invokedFunction = event.commonEventObject?.invokedFunction;\n if (buttonClickedPayload || invokedFunction) {\n this.handleCardClick(event, options);\n // For HTTP endpoint apps (Workspace Add-ons), return empty JSON to acknowledge.\n // The RenderActions format expects cards in google.apps.card.v1 format,\n // actionResponse is for the older Google Chat API format.\n // Returning {} acknowledges the action without changing the card.\n return new Response(JSON.stringify({}), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n // Check for message payload in the Add-ons format\n const messagePayload = event.chat?.messagePayload;\n if (messagePayload) {\n this.logger?.debug(\"message event\", {\n space: messagePayload.space.name,\n sender: messagePayload.message.sender?.displayName,\n text: messagePayload.message.text?.slice(0, 50),\n });\n this.handleMessageEvent(event, options);\n } else if (!addedPayload && !removedPayload) {\n this.logger?.debug(\"Non-message event received\", {\n hasChat: !!event.chat,\n hasCommonEventObject: !!event.commonEventObject,\n });\n }\n\n // Google Chat expects an empty response or a message response\n return new Response(JSON.stringify({}), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n /**\n * Handle Pub/Sub push messages from Workspace Events subscriptions.\n * These contain all messages in a space, not just @mentions.\n */\n private handlePubSubMessage(\n pushMessage: PubSubPushMessage,\n options?: WebhookOptions,\n ): Response {\n // Early filter: Check event type BEFORE base64 decoding to save CPU\n // The ce-type attribute is available in message.attributes\n const eventType = pushMessage.message?.attributes?.[\"ce-type\"];\n const allowedEventTypes = [\n \"google.workspace.chat.message.v1.created\",\n \"google.workspace.chat.reaction.v1.created\",\n \"google.workspace.chat.reaction.v1.deleted\",\n ];\n if (eventType && !allowedEventTypes.includes(eventType)) {\n this.logger?.debug(\"Skipping unsupported Pub/Sub event\", { eventType });\n return new Response(JSON.stringify({ success: true }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n try {\n const notification = decodePubSubMessage(pushMessage);\n this.logger?.debug(\"Pub/Sub notification decoded\", {\n eventType: notification.eventType,\n messageId: notification.message?.name,\n reactionName: notification.reaction?.name,\n });\n\n // Handle message.created events\n if (notification.message) {\n this.handlePubSubMessageEvent(notification, options);\n }\n\n // Handle reaction events\n if (notification.reaction) {\n this.handlePubSubReactionEvent(notification, options);\n }\n\n // Acknowledge the message\n return new Response(JSON.stringify({ success: true }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n this.logger?.error(\"Error processing Pub/Sub message\", { error });\n // Return 200 to avoid retries for malformed messages\n return new Response(JSON.stringify({ error: \"Processing failed\" }), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n }\n\n /**\n * Handle message events received via Pub/Sub (Workspace Events).\n */\n private handlePubSubMessageEvent(\n notification: WorkspaceEventNotification,\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !notification.message) {\n return;\n }\n\n const message = notification.message;\n // Extract space name from targetResource: \"//chat.googleapis.com/spaces/AAAA\"\n const spaceName = notification.targetResource?.replace(\n \"//chat.googleapis.com/\",\n \"\",\n );\n const threadName = message.thread?.name || message.name;\n const threadId = this.encodeThreadId({\n spaceName: spaceName || message.space?.name || \"\",\n threadName,\n });\n\n // Refresh subscription if needed (runs in background)\n const resolvedSpaceName = spaceName || message.space?.name;\n if (resolvedSpaceName && options?.waitUntil) {\n options.waitUntil(\n this.ensureSpaceSubscription(resolvedSpaceName).catch((err) => {\n this.logger?.debug(\"Subscription refresh failed\", { error: err });\n }),\n );\n }\n\n // Let Chat class handle async processing and waitUntil\n // Use factory function since parsePubSubMessage is async (user display name lookup)\n this.chat.processMessage(\n this,\n threadId,\n () => this.parsePubSubMessage(notification, threadId),\n options,\n );\n }\n\n /**\n * Handle reaction events received via Pub/Sub (Workspace Events).\n * Fetches the message to get thread context for proper reply threading.\n */\n private handlePubSubReactionEvent(\n notification: WorkspaceEventNotification,\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !notification.reaction) {\n return;\n }\n\n const reaction = notification.reaction;\n const rawEmoji = reaction.emoji?.unicode || \"\";\n const normalizedEmoji = defaultEmojiResolver.fromGChat(rawEmoji);\n\n // Extract message name from reaction name\n // Format: spaces/{space}/messages/{message}/reactions/{reaction}\n const reactionName = reaction.name || \"\";\n const messageNameMatch = reactionName.match(\n /(spaces\\/[^/]+\\/messages\\/[^/]+)/,\n );\n const messageName = messageNameMatch ? messageNameMatch[1] : \"\";\n\n // Extract space name from targetResource\n const spaceName = notification.targetResource?.replace(\n \"//chat.googleapis.com/\",\n \"\",\n );\n\n // Check if reaction is from this bot\n const isMe =\n this.botUserId !== undefined && reaction.user?.name === this.botUserId;\n\n // Determine if this is an add or remove\n const added = notification.eventType.includes(\"created\");\n\n // We need to fetch the message to get its thread context\n // This is done lazily when the reaction is processed\n const chat = this.chat;\n const buildReactionEvent = async (): Promise<\n Omit<ReactionEvent, \"adapter\" | \"thread\"> & { adapter: GoogleChatAdapter }\n > => {\n let threadId: string;\n\n // Fetch the message to get its thread name\n if (messageName) {\n try {\n const messageResponse = await this.chatApi.spaces.messages.get({\n name: messageName,\n });\n const threadName = messageResponse.data.thread?.name;\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n threadName: threadName ?? undefined,\n });\n this.logger?.debug(\"Fetched thread context for reaction\", {\n messageName,\n threadName,\n threadId,\n });\n } catch (error) {\n this.logger?.warn(\"Failed to fetch message for thread context\", {\n messageName,\n error,\n });\n // Fall back to space-only thread ID\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n });\n }\n } else {\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n });\n }\n\n return {\n emoji: normalizedEmoji,\n rawEmoji,\n added,\n user: {\n userId: reaction.user?.name || \"unknown\",\n userName: reaction.user?.displayName || \"unknown\",\n fullName: reaction.user?.displayName || \"unknown\",\n isBot: reaction.user?.type === \"BOT\",\n isMe,\n },\n messageId: messageName,\n threadId,\n raw: notification,\n adapter: this,\n };\n };\n\n // Process reaction with lazy thread resolution\n const processTask = buildReactionEvent().then((reactionEvent) => {\n chat.processReaction(reactionEvent, options);\n });\n\n if (options?.waitUntil) {\n options.waitUntil(processTask);\n }\n }\n\n /**\n * Parse a Pub/Sub message into the standard Message format.\n * Resolves user display names from cache since Pub/Sub messages don't include them.\n */\n private async parsePubSubMessage(\n notification: WorkspaceEventNotification,\n threadId: string,\n ): Promise<Message<unknown>> {\n const message = notification.message;\n if (!message) {\n throw new Error(\"PubSub notification missing message\");\n }\n const text = this.normalizeBotMentions(message);\n const isBot = message.sender?.type === \"BOT\";\n const isMe = this.isMessageFromSelf(message);\n\n // Pub/Sub messages don't include displayName - resolve from cache\n const userId = message.sender?.name || \"unknown\";\n const displayName = await this.resolveUserDisplayName(\n userId,\n message.sender?.displayName,\n );\n\n const parsedMessage: Message<unknown> = {\n id: message.name,\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: notification,\n author: {\n userId,\n userName: displayName,\n fullName: displayName,\n isBot,\n isMe,\n },\n metadata: {\n dateSent: new Date(message.createTime),\n edited: false,\n },\n attachments: (message.attachment || []).map((att) =>\n this.createAttachment(att),\n ),\n };\n\n this.logger?.debug(\"Pub/Sub parsed message\", {\n threadId,\n messageId: parsedMessage.id,\n text: parsedMessage.text,\n author: parsedMessage.author.fullName,\n isBot: parsedMessage.author.isBot,\n isMe: parsedMessage.author.isMe,\n });\n\n return parsedMessage;\n }\n\n /**\n * Handle bot being added to a space - create Workspace Events subscription.\n */\n private handleAddedToSpace(\n space: GoogleChatSpace,\n options?: WebhookOptions,\n ): void {\n const subscribeTask = this.ensureSpaceSubscription(space.name);\n\n if (options?.waitUntil) {\n options.waitUntil(subscribeTask);\n }\n }\n\n /**\n * Handle card button clicks.\n * For HTTP endpoint apps, the actionId is passed via parameters (since function is the URL).\n * For other deployments, actionId may be in invokedFunction.\n */\n private handleCardClick(\n event: GoogleChatEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring card click\");\n return;\n }\n\n const buttonPayload = event.chat?.buttonClickedPayload;\n const commonEvent = event.commonEventObject;\n\n // Get action ID - for HTTP endpoints it's in parameters.actionId,\n // for other deployments it may be in invokedFunction\n const actionId =\n commonEvent?.parameters?.actionId || commonEvent?.invokedFunction;\n if (!actionId) {\n this.logger?.debug(\"Card click missing actionId\", {\n parameters: commonEvent?.parameters,\n invokedFunction: commonEvent?.invokedFunction,\n });\n return;\n }\n\n // Get value from parameters\n const value = commonEvent?.parameters?.value;\n\n // Get space and message info from buttonClickedPayload\n const space = buttonPayload?.space;\n const message = buttonPayload?.message;\n const user = buttonPayload?.user || event.chat?.user;\n\n if (!space) {\n this.logger?.warn(\"Card click missing space info\");\n return;\n }\n\n const threadName = message?.thread?.name || message?.name;\n const threadId = this.encodeThreadId({\n spaceName: space.name,\n threadName,\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & {\n adapter: GoogleChatAdapter;\n } = {\n actionId,\n value,\n user: {\n userId: user?.name || \"unknown\",\n userName: user?.displayName || \"unknown\",\n fullName: user?.displayName || \"unknown\",\n isBot: user?.type === \"BOT\",\n isMe: false,\n },\n messageId: message?.name || \"\",\n threadId,\n adapter: this,\n raw: event,\n };\n\n this.logger?.debug(\"Processing GChat card click\", {\n actionId,\n value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n\n /**\n * Handle direct webhook message events (Add-ons format).\n */\n private handleMessageEvent(\n event: GoogleChatEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n const messagePayload = event.chat?.messagePayload;\n if (!messagePayload) {\n this.logger?.debug(\"Ignoring event without messagePayload\");\n return;\n }\n\n const message = messagePayload.message;\n // For DMs, use space-only thread ID so all messages in the DM\n // match the DM subscription created by openDM(). This treats the entire DM\n // conversation as a single \"thread\" for subscription purposes.\n const isDM =\n messagePayload.space.type === \"DM\" ||\n messagePayload.space.spaceType === \"DIRECT_MESSAGE\";\n const threadName = isDM ? undefined : message.thread?.name || message.name;\n const threadId = this.encodeThreadId({\n spaceName: messagePayload.space.name,\n threadName,\n isDM,\n });\n\n // Let Chat class handle async processing and waitUntil\n this.chat.processMessage(\n this,\n threadId,\n this.parseGoogleChatMessage(event, threadId),\n options,\n );\n }\n\n private parseGoogleChatMessage(\n event: GoogleChatEvent,\n threadId: string,\n ): Message<unknown> {\n const message = event.chat?.messagePayload?.message;\n if (!message) {\n throw new Error(\"Event has no message payload\");\n }\n\n // Normalize bot mentions: replace @BotDisplayName with @{userName}\n // so the Chat SDK's mention detection works properly\n const text = this.normalizeBotMentions(message);\n\n const isBot = message.sender?.type === \"BOT\";\n const isMe = this.isMessageFromSelf(message);\n\n // Cache user info for future Pub/Sub messages (which don't include displayName)\n const userId = message.sender?.name || \"unknown\";\n const displayName = message.sender?.displayName || \"unknown\";\n if (userId !== \"unknown\" && displayName !== \"unknown\") {\n this.cacheUserInfo(userId, displayName, message.sender?.email).catch(\n () => {},\n );\n }\n\n return {\n id: message.name,\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId,\n userName: displayName,\n fullName: displayName,\n isBot,\n isMe,\n },\n metadata: {\n dateSent: new Date(message.createTime),\n edited: false,\n },\n attachments: (message.attachment || []).map((att) =>\n this.createAttachment(att),\n ),\n };\n }\n\n async postMessage(\n threadId: string,\n message: PostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { spaceName, threadName } = this.decodeThreadId(threadId);\n\n try {\n // Check for files - currently not implemented for GChat\n const files = this.extractFiles(message);\n if (files.length > 0) {\n this.logger?.warn(\n \"File uploads are not yet supported for Google Chat. Files will be ignored.\",\n { fileCount: files.length },\n );\n // TODO: Implement using Google Chat media.upload API\n }\n\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Google Chat Card\n // cardId is required for interactive cards (button clicks)\n // endpointUrl is required for HTTP endpoint apps to route button clicks\n const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n const googleCard = cardToGoogleCard(card, {\n cardId,\n endpointUrl: this.endpointUrl,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create (card)\", {\n spaceName,\n threadName,\n googleCard: JSON.stringify(googleCard),\n });\n\n const response = await this.chatApi.spaces.messages.create({\n parent: spaceName,\n messageReplyOption: threadName\n ? \"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD\"\n : undefined,\n requestBody: {\n // Don't include text - GChat shows both text and card if text is present\n cardsV2: [googleCard],\n thread: threadName ? { name: threadName } : undefined,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"gchat\",\n );\n\n this.logger?.debug(\"GChat API: spaces.messages.create\", {\n spaceName,\n threadName,\n textLength: text.length,\n });\n\n const response = await this.chatApi.spaces.messages.create({\n parent: spaceName,\n // Required to reply in an existing thread\n messageReplyOption: threadName\n ? \"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD\"\n : undefined,\n requestBody: {\n text,\n thread: threadName ? { name: threadName } : undefined,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"postMessage\");\n }\n }\n\n /**\n * Extract card element from a PostableMessage if present.\n */\n private extractCard(\n message: PostableMessage,\n ): import(\"chat\").CardElement | null {\n if (isCardElement(message)) {\n return message;\n }\n if (typeof message === \"object\" && message !== null && \"card\" in message) {\n return message.card;\n }\n return null;\n }\n\n /**\n * Extract files from a PostableMessage if present.\n */\n private extractFiles(message: PostableMessage): FileUpload[] {\n if (typeof message === \"object\" && message !== null && \"files\" in message) {\n return (message as { files?: FileUpload[] }).files ?? [];\n }\n return [];\n }\n\n /**\n * Create an Attachment object from a Google Chat attachment.\n */\n private createAttachment(att: {\n contentType?: string | null;\n downloadUri?: string | null;\n contentName?: string | null;\n thumbnailUri?: string | null;\n }): Attachment {\n const url = att.downloadUri || undefined;\n const authClient = this.authClient;\n\n // Determine type based on contentType\n let type: Attachment[\"type\"] = \"file\";\n if (att.contentType?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (att.contentType?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (att.contentType?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n // Capture auth client for use in fetchData closure\n const auth = authClient;\n\n return {\n type,\n url,\n name: att.contentName || undefined,\n mimeType: att.contentType || undefined,\n fetchData: url\n ? async () => {\n // Get access token for authenticated download\n if (typeof auth === \"string\" || !auth) {\n throw new Error(\"Cannot fetch file: no auth client configured\");\n }\n const tokenResult = await auth.getAccessToken();\n const token =\n typeof tokenResult === \"string\"\n ? tokenResult\n : tokenResult?.token;\n if (!token) {\n throw new Error(\"Failed to get access token\");\n }\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: PostableMessage,\n ): Promise<RawMessage<unknown>> {\n try {\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Google Chat Card\n // cardId is required for interactive cards (button clicks)\n // endpointUrl is required for HTTP endpoint apps to route button clicks\n const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n const googleCard = cardToGoogleCard(card, {\n cardId,\n endpointUrl: this.endpointUrl,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update (card)\", {\n messageId,\n cardId,\n });\n\n const response = await this.chatApi.spaces.messages.update({\n name: messageId,\n updateMask: \"cardsV2\",\n requestBody: {\n // Don't include text - GChat shows both text and card if text is present\n cardsV2: [googleCard],\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"gchat\",\n );\n\n this.logger?.debug(\"GChat API: spaces.messages.update\", {\n messageId,\n textLength: text.length,\n });\n\n const response = await this.chatApi.spaces.messages.update({\n name: messageId,\n updateMask: \"text\",\n requestBody: {\n text,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"editMessage\");\n }\n }\n\n async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n try {\n this.logger?.debug(\"GChat API: spaces.messages.delete\", { messageId });\n\n await this.chatApi.spaces.messages.delete({\n name: messageId,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.delete response\", {\n ok: true,\n });\n } catch (error) {\n this.handleGoogleChatError(error, \"deleteMessage\");\n }\n }\n\n async addReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n // Convert emoji (EmojiValue or string) to GChat unicode format\n const gchatEmoji = defaultEmojiResolver.toGChat(emoji);\n\n try {\n this.logger?.debug(\"GChat API: spaces.messages.reactions.create\", {\n messageId,\n emoji: gchatEmoji,\n });\n\n await this.chatApi.spaces.messages.reactions.create({\n parent: messageId,\n requestBody: {\n emoji: { unicode: gchatEmoji },\n },\n });\n\n this.logger?.debug(\n \"GChat API: spaces.messages.reactions.create response\",\n {\n ok: true,\n },\n );\n } catch (error) {\n this.handleGoogleChatError(error, \"addReaction\");\n }\n }\n\n async removeReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n // Convert emoji (EmojiValue or string) to GChat unicode format\n const gchatEmoji = defaultEmojiResolver.toGChat(emoji);\n\n try {\n // Google Chat requires the reaction resource name to delete it.\n // We need to list reactions and find the one with matching emoji.\n this.logger?.debug(\"GChat API: spaces.messages.reactions.list\", {\n messageId,\n });\n\n const response = await this.chatApi.spaces.messages.reactions.list({\n parent: messageId,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.reactions.list response\", {\n reactionCount: response.data.reactions?.length || 0,\n });\n\n const reaction = response.data.reactions?.find(\n (r) => r.emoji?.unicode === gchatEmoji,\n );\n\n if (!reaction?.name) {\n this.logger?.debug(\"Reaction not found to remove\", {\n messageId,\n emoji: gchatEmoji,\n });\n return;\n }\n\n this.logger?.debug(\"GChat API: spaces.messages.reactions.delete\", {\n reactionName: reaction.name,\n });\n\n await this.chatApi.spaces.messages.reactions.delete({\n name: reaction.name,\n });\n\n this.logger?.debug(\n \"GChat API: spaces.messages.reactions.delete response\",\n {\n ok: true,\n },\n );\n } catch (error) {\n this.handleGoogleChatError(error, \"removeReaction\");\n }\n }\n\n async startTyping(_threadId: string): Promise<void> {\n // Google Chat doesn't have a typing indicator API for bots\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n *\n * For Google Chat, this first tries to find an existing DM space with the user.\n * If no DM exists, it creates one using spaces.setup.\n *\n * @param userId - The user's resource name (e.g., \"users/123456\")\n */\n async openDM(userId: string): Promise<string> {\n try {\n // First, try to find an existing DM space with this user\n // This works with the bot's own credentials (no impersonation needed)\n this.logger?.debug(\"GChat API: spaces.findDirectMessage\", { userId });\n\n const findResponse = await this.chatApi.spaces.findDirectMessage({\n name: userId,\n });\n\n if (findResponse.data.name) {\n this.logger?.debug(\"GChat API: Found existing DM space\", {\n spaceName: findResponse.data.name,\n });\n return this.encodeThreadId({\n spaceName: findResponse.data.name,\n isDM: true,\n });\n }\n } catch (error) {\n // 404 means no DM exists yet - we'll try to create one\n const gError = error as { code?: number };\n if (gError.code !== 404) {\n this.logger?.debug(\"GChat API: findDirectMessage failed\", { error });\n }\n }\n\n // No existing DM found - try to create one\n // Use impersonated API if available (required for creating new DMs)\n const chatApi = this.impersonatedChatApi || this.chatApi;\n\n if (!this.impersonatedChatApi) {\n this.logger?.warn(\n \"openDM: No existing DM found and no impersonation configured. \" +\n \"Creating new DMs requires domain-wide delegation. \" +\n \"Set 'impersonateUser' in adapter config.\",\n );\n }\n\n try {\n this.logger?.debug(\"GChat API: spaces.setup (DM)\", {\n userId,\n hasImpersonation: !!this.impersonatedChatApi,\n impersonateUser: this.impersonateUser,\n });\n\n // Create a DM space between the impersonated user and the target user\n // Don't use singleUserBotDm - that's for DMs with the bot itself\n const response = await chatApi.spaces.setup({\n requestBody: {\n space: {\n spaceType: \"DIRECT_MESSAGE\",\n },\n memberships: [\n {\n member: {\n name: userId,\n type: \"HUMAN\",\n },\n },\n ],\n },\n });\n\n const spaceName = response.data.name;\n\n if (!spaceName) {\n throw new Error(\"Failed to create DM - no space name returned\");\n }\n\n this.logger?.debug(\"GChat API: spaces.setup response\", { spaceName });\n\n return this.encodeThreadId({ spaceName, isDM: true });\n } catch (error) {\n this.handleGoogleChatError(error, \"openDM\");\n }\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<Message<unknown>[]> {\n const { spaceName } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"GChat API: spaces.messages.list\", {\n spaceName,\n pageSize: options.limit || 100,\n });\n\n const response = await this.chatApi.spaces.messages.list({\n parent: spaceName,\n pageSize: options.limit || 100,\n pageToken: options.before,\n });\n\n const messages = response.data.messages || [];\n\n this.logger?.debug(\"GChat API: spaces.messages.list response\", {\n messageCount: messages.length,\n });\n\n return messages.map((msg) => {\n const msgThreadId = this.encodeThreadId({\n spaceName,\n threadName: msg.thread?.name ?? undefined,\n });\n const msgIsBot = msg.sender?.type === \"BOT\";\n return {\n id: msg.name || \"\",\n threadId: msgThreadId,\n text: this.formatConverter.extractPlainText(msg.text || \"\"),\n formatted: this.formatConverter.toAst(msg.text || \"\"),\n raw: msg,\n author: {\n userId: msg.sender?.name || \"unknown\",\n userName: msg.sender?.displayName || \"unknown\",\n fullName: msg.sender?.displayName || \"unknown\",\n isBot: msgIsBot,\n isMe: msgIsBot,\n },\n metadata: {\n dateSent: msg.createTime ? new Date(msg.createTime) : new Date(),\n edited: false,\n },\n attachments: [],\n };\n });\n } catch (error) {\n this.handleGoogleChatError(error, \"fetchMessages\");\n }\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { spaceName } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"GChat API: spaces.get\", { spaceName });\n\n const response = await this.chatApi.spaces.get({ name: spaceName });\n\n this.logger?.debug(\"GChat API: spaces.get response\", {\n displayName: response.data.displayName,\n });\n\n return {\n id: threadId,\n channelId: spaceName,\n channelName: response.data.displayName ?? undefined,\n metadata: {\n space: response.data,\n },\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"fetchThread\");\n }\n }\n\n encodeThreadId(platformData: GoogleChatThreadId): string {\n const threadPart = platformData.threadName\n ? `:${Buffer.from(platformData.threadName).toString(\"base64url\")}`\n : \"\";\n // Add :dm suffix for DM threads to enable isDM() detection\n const dmPart = platformData.isDM ? \":dm\" : \"\";\n return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Checks for the :dm marker in the thread ID which is set when\n * processing DM messages or opening DMs.\n */\n isDM(threadId: string): boolean {\n // Check for explicit :dm marker in thread ID\n return threadId.endsWith(\":dm\");\n }\n\n decodeThreadId(threadId: string): GoogleChatThreadId {\n // Remove :dm suffix if present\n const isDM = threadId.endsWith(\":dm\");\n const cleanId = isDM ? threadId.slice(0, -3) : threadId;\n\n const parts = cleanId.split(\":\");\n if (parts.length < 2 || parts[0] !== \"gchat\") {\n throw new Error(`Invalid Google Chat thread ID: ${threadId}`);\n }\n\n const spaceName = parts[1] as string;\n const threadName = parts[2]\n ? Buffer.from(parts[2], \"base64url\").toString(\"utf-8\")\n : undefined;\n\n return { spaceName, threadName, isDM };\n }\n\n parseMessage(raw: unknown): Message<unknown> {\n const event = raw as GoogleChatEvent;\n const messagePayload = event.chat?.messagePayload;\n if (!messagePayload) {\n throw new Error(\"Cannot parse non-message event\");\n }\n const threadName =\n messagePayload.message.thread?.name || messagePayload.message.name;\n const threadId = this.encodeThreadId({\n spaceName: messagePayload.space.name,\n threadName,\n });\n return this.parseGoogleChatMessage(event, threadId);\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Normalize bot mentions in message text.\n * Google Chat uses the bot's display name (e.g., \"@Chat SDK Demo\") but the\n * Chat SDK expects \"@{userName}\" format. This method replaces bot mentions\n * with the adapter's userName so mention detection works properly.\n * Also learns the bot's user ID from annotations for isMe detection.\n */\n private normalizeBotMentions(message: GoogleChatMessage): string {\n let text = message.text || \"\";\n\n // Find bot mentions in annotations and replace with @{userName}\n const annotations = message.annotations || [];\n for (const annotation of annotations) {\n if (\n annotation.type === \"USER_MENTION\" &&\n annotation.userMention?.user?.type === \"BOT\"\n ) {\n const botUser = annotation.userMention.user;\n const botDisplayName = botUser.displayName;\n\n // Learn our bot's user ID from mentions and persist to state\n if (botUser.name && !this.botUserId) {\n this.botUserId = botUser.name;\n this.logger?.info(\"Learned bot user ID from mention\", {\n botUserId: this.botUserId,\n });\n // Persist to state for serverless environments\n this.state\n ?.set(\"gchat:botUserId\", this.botUserId)\n .catch((err) =>\n this.logger?.debug(\"Failed to persist botUserId\", { error: err }),\n );\n }\n\n // Replace the bot mention with @{userName}\n // Pub/Sub messages don't include displayName, so use startIndex/length\n if (\n annotation.startIndex !== undefined &&\n annotation.length !== undefined\n ) {\n const startIndex = annotation.startIndex;\n const length = annotation.length;\n const mentionText = text.slice(startIndex, startIndex + length);\n text =\n text.slice(0, startIndex) +\n `@${this.userName}` +\n text.slice(startIndex + length);\n this.logger?.debug(\"Normalized bot mention\", {\n original: mentionText,\n replacement: `@${this.userName}`,\n });\n } else if (botDisplayName) {\n // Fallback: use displayName if available (direct webhook)\n const mentionText = `@${botDisplayName}`;\n text = text.replace(mentionText, `@${this.userName}`);\n }\n }\n }\n\n return text;\n }\n\n /**\n * Check if a message is from this bot.\n *\n * Bot user ID is learned dynamically from message annotations when the bot\n * is @mentioned. Until we learn the ID, we cannot reliably determine isMe.\n *\n * This is safer than the previous approach of assuming all BOT messages are\n * from self, which would incorrectly filter messages from other bots in\n * multi-bot spaces (especially via Pub/Sub).\n */\n private isMessageFromSelf(message: GoogleChatMessage): boolean {\n const senderId = message.sender?.name;\n\n // Use exact match when we know our bot ID\n if (this.botUserId && senderId) {\n return senderId === this.botUserId;\n }\n\n // If we don't know our bot ID yet, we can't reliably determine isMe.\n // Log a debug message and return false - better to process a self-message\n // than to incorrectly filter out messages from other bots.\n if (!this.botUserId && message.sender?.type === \"BOT\") {\n this.logger?.debug(\n \"Cannot determine isMe - bot user ID not yet learned. \" +\n \"Bot ID is learned from @mentions. Assuming message is not from self.\",\n { senderId },\n );\n }\n\n return false;\n }\n\n /**\n * Cache user info for later lookup (e.g., when processing Pub/Sub messages).\n */\n private async cacheUserInfo(\n userId: string,\n displayName: string,\n email?: string,\n ): Promise<void> {\n if (!this.state || !displayName || displayName === \"unknown\") return;\n\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n await this.state.set<CachedUserInfo>(\n cacheKey,\n { displayName, email },\n USER_INFO_CACHE_TTL_MS,\n );\n }\n\n /**\n * Get cached user info.\n */\n private async getCachedUserInfo(\n userId: string,\n ): Promise<CachedUserInfo | null> {\n if (!this.state) return null;\n\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n return this.state.get<CachedUserInfo>(cacheKey);\n }\n\n /**\n * Resolve user display name, using cache if available.\n */\n private async resolveUserDisplayName(\n userId: string,\n providedDisplayName?: string,\n ): Promise<string> {\n // If display name is provided and not \"unknown\", use it\n if (providedDisplayName && providedDisplayName !== \"unknown\") {\n // Also cache it for future use\n this.cacheUserInfo(userId, providedDisplayName).catch(() => {});\n return providedDisplayName;\n }\n\n // Try to get from cache\n const cached = await this.getCachedUserInfo(userId);\n if (cached?.displayName) {\n return cached.displayName;\n }\n\n // Fall back to extracting name from userId (e.g., \"users/123\" -> \"User 123\")\n return userId.replace(\"users/\", \"User \");\n }\n\n private handleGoogleChatError(error: unknown, context?: string): never {\n const gError = error as {\n code?: number;\n message?: string;\n errors?: unknown;\n };\n\n // Log the error at error level for visibility\n this.logger?.error(`GChat API error${context ? ` (${context})` : \"\"}`, {\n code: gError.code,\n message: gError.message,\n errors: gError.errors,\n error,\n });\n\n if (gError.code === 429) {\n throw new RateLimitError(\n \"Google Chat rate limit exceeded\",\n undefined,\n error,\n );\n }\n\n throw error;\n }\n}\n\nexport function createGoogleChatAdapter(\n config: GoogleChatAdapterConfig,\n): GoogleChatAdapter {\n return new GoogleChatAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToFallbackText, cardToGoogleCard } from \"./cards\";\nexport { GoogleChatFormatConverter } from \"./markdown\";\n\nexport {\n type CreateSpaceSubscriptionOptions,\n createSpaceSubscription,\n decodePubSubMessage,\n deleteSpaceSubscription,\n listSpaceSubscriptions,\n type PubSubPushMessage,\n type SpaceSubscriptionResult,\n verifyPubSubRequest,\n type WorkspaceEventNotification,\n type WorkspaceEventsAuthOptions,\n} from \"./workspace-events\";\n","/**\n * Google Chat Card converter for cross-platform cards.\n *\n * Converts CardElement to Google Chat Card v2 format.\n * @see https://developers.google.com/chat/api/reference/rest/v1/cards\n */\n\nimport {\n type ActionsElement,\n type ButtonElement,\n type CardChild,\n type CardElement,\n convertEmojiPlaceholders,\n type DividerElement,\n type FieldsElement,\n type ImageElement,\n type SectionElement,\n type TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to GChat format (Unicode).\n */\nfunction convertEmoji(text: string): string {\n return convertEmojiPlaceholders(text, \"gchat\");\n}\n\n// Google Chat Card v2 types (simplified)\nexport interface GoogleChatCard {\n cardId?: string;\n card: {\n header?: GoogleChatCardHeader;\n sections: GoogleChatCardSection[];\n };\n}\n\nexport interface GoogleChatCardHeader {\n title: string;\n subtitle?: string;\n imageUrl?: string;\n imageType?: \"CIRCLE\" | \"SQUARE\";\n}\n\nexport interface GoogleChatCardSection {\n header?: string;\n widgets: GoogleChatWidget[];\n collapsible?: boolean;\n}\n\nexport interface GoogleChatWidget {\n textParagraph?: { text: string };\n image?: { imageUrl: string; altText?: string };\n decoratedText?: {\n topLabel?: string;\n text: string;\n bottomLabel?: string;\n startIcon?: { knownIcon?: string };\n };\n buttonList?: { buttons: GoogleChatButton[] };\n divider?: Record<string, never>;\n}\n\nexport interface GoogleChatButton {\n text: string;\n onClick: {\n action: {\n function: string;\n parameters: Array<{ key: string; value: string }>;\n };\n };\n color?: { red: number; green: number; blue: number };\n}\n\n/**\n * Options for card conversion.\n */\nexport interface CardConversionOptions {\n /** Unique card ID for interactive cards */\n cardId?: string;\n /**\n * HTTP endpoint URL for button actions.\n * Required for HTTP endpoint apps - button clicks will be routed to this URL.\n */\n endpointUrl?: string;\n}\n\n/**\n * Convert a CardElement to Google Chat Card v2 format.\n */\nexport function cardToGoogleCard(\n card: CardElement,\n options?: CardConversionOptions | string,\n): GoogleChatCard {\n // Support legacy signature where second arg is cardId string\n const opts: CardConversionOptions =\n typeof options === \"string\" ? { cardId: options } : options || {};\n\n const sections: GoogleChatCardSection[] = [];\n\n // Build header\n let header: GoogleChatCardHeader | undefined;\n if (card.title || card.subtitle || card.imageUrl) {\n header = {\n title: convertEmoji(card.title || \"\"),\n };\n if (card.subtitle) {\n header.subtitle = convertEmoji(card.subtitle);\n }\n if (card.imageUrl) {\n header.imageUrl = card.imageUrl;\n header.imageType = \"SQUARE\";\n }\n }\n\n // Group children into sections\n // GChat cards require widgets to be inside sections\n let currentWidgets: GoogleChatWidget[] = [];\n\n for (const child of card.children) {\n if (child.type === \"section\") {\n // If we have pending widgets, flush them to a section\n if (currentWidgets.length > 0) {\n sections.push({ widgets: currentWidgets });\n currentWidgets = [];\n }\n // Convert section as its own section\n const sectionWidgets = convertSectionToWidgets(child, opts.endpointUrl);\n sections.push({ widgets: sectionWidgets });\n } else {\n // Add to current widgets\n const widgets = convertChildToWidgets(child, opts.endpointUrl);\n currentWidgets.push(...widgets);\n }\n }\n\n // Flush remaining widgets\n if (currentWidgets.length > 0) {\n sections.push({ widgets: currentWidgets });\n }\n\n // GChat requires at least one section with at least one widget\n if (sections.length === 0) {\n sections.push({\n widgets: [{ textParagraph: { text: \"\" } }],\n });\n }\n\n const googleCard: GoogleChatCard = {\n card: {\n sections,\n },\n };\n\n if (header) {\n googleCard.card.header = header;\n }\n\n if (opts.cardId) {\n googleCard.cardId = opts.cardId;\n }\n\n return googleCard;\n}\n\n/**\n * Convert a card child element to Google Chat widgets.\n */\nfunction convertChildToWidgets(\n child: CardChild,\n endpointUrl?: string,\n): GoogleChatWidget[] {\n switch (child.type) {\n case \"text\":\n return [convertTextToWidget(child)];\n case \"image\":\n return [convertImageToWidget(child)];\n case \"divider\":\n return [convertDividerToWidget(child)];\n case \"actions\":\n return [convertActionsToWidget(child, endpointUrl)];\n case \"section\":\n return convertSectionToWidgets(child, endpointUrl);\n case \"fields\":\n return convertFieldsToWidgets(child);\n default:\n return [];\n }\n}\n\nfunction convertTextToWidget(element: TextElement): GoogleChatWidget {\n let text = convertEmoji(element.content);\n\n // Apply style using Google Chat formatting\n if (element.style === \"bold\") {\n text = `*${text}*`;\n } else if (element.style === \"muted\") {\n // GChat doesn't have muted, use regular text\n text = convertEmoji(element.content);\n }\n\n return {\n textParagraph: { text },\n };\n}\n\nfunction convertImageToWidget(element: ImageElement): GoogleChatWidget {\n return {\n image: {\n imageUrl: element.url,\n altText: element.alt || \"Image\",\n },\n };\n}\n\nfunction convertDividerToWidget(_element: DividerElement): GoogleChatWidget {\n return { divider: {} };\n}\n\nfunction convertActionsToWidget(\n element: ActionsElement,\n endpointUrl?: string,\n): GoogleChatWidget {\n const buttons: GoogleChatButton[] = element.children.map((button) =>\n convertButtonToGoogleButton(button, endpointUrl),\n );\n\n return {\n buttonList: { buttons },\n };\n}\n\nfunction convertButtonToGoogleButton(\n button: ButtonElement,\n endpointUrl?: string,\n): GoogleChatButton {\n // For HTTP endpoint apps, the function field must be the endpoint URL,\n // and the action ID is passed via parameters.\n // See: https://developers.google.com/workspace/add-ons/chat/dialogs\n const parameters: Array<{ key: string; value: string }> = [\n { key: \"actionId\", value: button.id },\n ];\n if (button.value) {\n parameters.push({ key: \"value\", value: button.value });\n }\n\n const googleButton: GoogleChatButton = {\n text: convertEmoji(button.label),\n onClick: {\n action: {\n // For HTTP endpoints, function must be the full URL\n // For other deployments (Apps Script, etc.), use just the action ID\n function: endpointUrl || button.id,\n parameters,\n },\n },\n };\n\n // Apply button style colors\n if (button.style === \"primary\") {\n // Blue color for primary\n googleButton.color = { red: 0.2, green: 0.5, blue: 0.9 };\n } else if (button.style === \"danger\") {\n // Red color for danger\n googleButton.color = { red: 0.9, green: 0.2, blue: 0.2 };\n }\n\n return googleButton;\n}\n\nfunction convertSectionToWidgets(\n element: SectionElement,\n endpointUrl?: string,\n): GoogleChatWidget[] {\n const widgets: GoogleChatWidget[] = [];\n for (const child of element.children) {\n widgets.push(...convertChildToWidgets(child, endpointUrl));\n }\n return widgets;\n}\n\nfunction convertFieldsToWidgets(element: FieldsElement): GoogleChatWidget[] {\n // Convert fields to decorated text widgets\n return element.children.map((field) => ({\n decoratedText: {\n topLabel: convertEmoji(field.label),\n text: convertEmoji(field.value),\n },\n }));\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when cards aren't supported.\n */\nexport function cardToFallbackText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(`*${convertEmoji(card.title)}*`);\n }\n\n if (card.subtitle) {\n parts.push(convertEmoji(card.subtitle));\n }\n\n for (const child of card.children) {\n const text = childToFallbackText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction childToFallbackText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return convertEmoji(child.content);\n case \"fields\":\n return child.children\n .map((f) => `*${convertEmoji(f.label)}*: ${convertEmoji(f.value)}`)\n .join(\"\\n\");\n case \"actions\":\n return `[${child.children\n .map((b) => convertEmoji(b.label))\n .join(\"] [\")}]`;\n case \"section\":\n return child.children\n .map((c) => childToFallbackText(c))\n .filter(Boolean)\n .join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Google Chat-specific format conversion using AST-based parsing.\n *\n * Google Chat supports a subset of text formatting:\n * - Bold: *text*\n * - Italic: _text_\n * - Strikethrough: ~text~\n * - Monospace: `text`\n * - Code blocks: ```text```\n * - Links are auto-detected\n *\n * Very similar to Slack's mrkdwn format.\n */\n\nimport {\n BaseFormatConverter,\n type Code,\n type Content,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Link,\n type Paragraph,\n parseMarkdown,\n type Root,\n type Strong,\n type Text,\n} from \"chat\";\n\nexport class GoogleChatFormatConverter extends BaseFormatConverter {\n /**\n * Render an AST to Google Chat format.\n */\n fromAst(ast: Root): string {\n const parts: string[] = [];\n\n for (const node of ast.children) {\n parts.push(this.nodeToGChat(node as Content));\n }\n\n return parts.join(\"\\n\\n\");\n }\n\n /**\n * Parse Google Chat message into an AST.\n */\n toAst(gchatText: string): Root {\n // Convert Google Chat format to standard markdown, then parse\n let markdown = gchatText;\n\n // Bold: *text* -> **text**\n markdown = markdown.replace(/(?<![_*\\\\])\\*([^*\\n]+)\\*(?![_*])/g, \"**$1**\");\n\n // Strikethrough: ~text~ -> ~~text~~\n markdown = markdown.replace(/(?<!~)~([^~\\n]+)~(?!~)/g, \"~~$1~~\");\n\n // Italic and code are the same format as markdown\n\n return parseMarkdown(markdown);\n }\n\n private nodeToGChat(node: Content): string {\n switch (node.type) {\n case \"paragraph\":\n return (node as Paragraph).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n\n case \"text\": {\n // Google Chat: @mentions are passed through as-is\n // To create clickable mentions in Google Chat, you'd need to use <users/{user_id}> format\n // which requires user ID lookup - beyond the scope of format conversion\n return (node as Text).value;\n }\n\n case \"strong\":\n // Markdown **text** -> GChat *text*\n return `*${(node as Strong).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}*`;\n\n case \"emphasis\":\n // Both use _text_\n return `_${(node as Emphasis).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}_`;\n\n case \"delete\":\n // Markdown ~~text~~ -> GChat ~text~\n return `~${(node as Delete).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}~`;\n\n case \"inlineCode\":\n return `\\`${(node as InlineCode).value}\\``;\n\n case \"code\": {\n const codeNode = node as Code;\n return `\\`\\`\\`\\n${codeNode.value}\\n\\`\\`\\``;\n }\n\n case \"link\": {\n // Google Chat auto-detects links, so we just output the URL\n const linkNode = node as Link;\n const linkText = linkNode.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n // If link text matches URL, just output URL\n if (linkText === linkNode.url) {\n return linkNode.url;\n }\n // Otherwise output \"text (url)\"\n return `${linkText} (${linkNode.url})`;\n }\n\n case \"blockquote\":\n // Google Chat doesn't have native blockquote, use > prefix\n return node.children\n .map((child) => `> ${this.nodeToGChat(child as Content)}`)\n .join(\"\\n\");\n\n case \"list\":\n return node.children\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"•\";\n const content = item.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n\n case \"listItem\":\n return node.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n\n case \"break\":\n return \"\\n\";\n\n case \"thematicBreak\":\n return \"---\";\n\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n return node.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n }\n if (\"value\" in node) {\n return String(node.value);\n }\n return \"\";\n }\n }\n}\n","/**\n * Google Workspace Events API integration for receiving all messages in a space.\n *\n * By default, Google Chat only sends webhooks for @mentions. To receive ALL messages\n * in a space, you need to create a Workspace Events subscription that publishes to\n * a Pub/Sub topic, which then pushes to your webhook endpoint.\n *\n * Setup flow:\n * 1. Create a Pub/Sub topic in your GCP project\n * 2. Create a Pub/Sub push subscription pointing to your /api/webhooks/gchat/pubsub endpoint\n * 3. Call createSpaceSubscription() to subscribe to message events for a space\n * 4. Handle Pub/Sub messages in your webhook with handlePubSubMessage()\n */\n\nimport { google } from \"googleapis\";\nimport type { GoogleChatMessage } from \"./index\";\n\n/** Options for creating a space subscription */\nexport interface CreateSpaceSubscriptionOptions {\n /** The space name (e.g., \"spaces/AAAA...\") */\n spaceName: string;\n /** The Pub/Sub topic to receive events (e.g., \"projects/my-project/topics/my-topic\") */\n pubsubTopic: string;\n /** Optional TTL for the subscription in seconds (default: 1 day, max: 1 day for Chat) */\n ttlSeconds?: number;\n}\n\n/** Result of creating a space subscription */\nexport interface SpaceSubscriptionResult {\n /** The subscription resource name */\n name: string;\n /** When the subscription expires (ISO 8601) */\n expireTime: string;\n}\n\n/** Pub/Sub push message wrapper (what Google sends to your endpoint) */\nexport interface PubSubPushMessage {\n message: {\n /** Base64 encoded event data */\n data: string;\n messageId: string;\n publishTime: string;\n attributes?: Record<string, string>;\n };\n subscription: string;\n}\n\n/** Google Chat reaction data */\nexport interface GoogleChatReaction {\n /** Reaction resource name */\n name: string;\n /** The user who added/removed the reaction */\n user?: {\n name: string;\n displayName?: string;\n type?: string;\n };\n /** The emoji */\n emoji?: {\n unicode?: string;\n };\n}\n\n/** Decoded Workspace Events notification payload */\nexport interface WorkspaceEventNotification {\n /** The subscription that triggered this event */\n subscription: string;\n /** The resource being watched (e.g., \"//chat.googleapis.com/spaces/AAAA\") */\n targetResource: string;\n /** Event type (e.g., \"google.workspace.chat.message.v1.created\") */\n eventType: string;\n /** When the event occurred */\n eventTime: string;\n /** Space info */\n space?: {\n name: string;\n type: string;\n };\n /** Present for message.created events */\n message?: GoogleChatMessage;\n /** Present for reaction.created/deleted events */\n reaction?: GoogleChatReaction;\n}\n\n/** Service account credentials for authentication */\nexport interface ServiceAccountCredentials {\n client_email: string;\n private_key: string;\n project_id?: string;\n}\n\n/** Auth options - service account, ADC, or custom auth client */\nexport type WorkspaceEventsAuthOptions =\n | { credentials: ServiceAccountCredentials; impersonateUser?: string }\n | { useApplicationDefaultCredentials: true; impersonateUser?: string }\n | { auth: Parameters<typeof google.workspaceevents>[0][\"auth\"] };\n\n/**\n * Create a Workspace Events subscription to receive all messages in a Chat space.\n *\n * Prerequisites:\n * - Enable the \"Google Workspace Events API\" in your GCP project\n * - Create a Pub/Sub topic and grant the Chat service account publish permissions\n * - The calling user/service account needs permission to access the space\n *\n * @example\n * ```typescript\n * const result = await createSpaceSubscription({\n * spaceName: \"spaces/AAAAxxxxxx\",\n * pubsubTopic: \"projects/my-project/topics/chat-events\",\n * }, {\n * credentials: {\n * client_email: \"...\",\n * private_key: \"...\",\n * }\n * });\n * ```\n */\nexport async function createSpaceSubscription(\n options: CreateSpaceSubscriptionOptions,\n auth: WorkspaceEventsAuthOptions,\n): Promise<SpaceSubscriptionResult> {\n const { spaceName, pubsubTopic, ttlSeconds = 86400 } = options; // Default 1 day\n\n // Set up auth\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n // For domain-wide delegation, impersonate a user\n subject: auth.impersonateUser,\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces.readonly\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n // Note: ADC with impersonation requires different setup\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces.readonly\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n // Create the subscription\n const response = await workspaceEvents.subscriptions.create({\n requestBody: {\n targetResource: `//chat.googleapis.com/${spaceName}`,\n eventTypes: [\n \"google.workspace.chat.message.v1.created\",\n \"google.workspace.chat.message.v1.updated\",\n \"google.workspace.chat.reaction.v1.created\",\n \"google.workspace.chat.reaction.v1.deleted\",\n ],\n notificationEndpoint: {\n pubsubTopic,\n },\n payloadOptions: {\n includeResource: true,\n },\n ttl: `${ttlSeconds}s`,\n },\n });\n\n // The create operation returns a long-running operation\n // For simplicity, we'll return the operation name - in production you might want to poll for completion\n const operation = response.data;\n\n if (operation.done && operation.response) {\n const subscription = operation.response as {\n name?: string;\n expireTime?: string;\n };\n return {\n name: subscription.name || \"\",\n expireTime: subscription.expireTime || \"\",\n };\n }\n\n // Operation is still pending - return operation name\n // The subscription will be created asynchronously\n return {\n name: operation.name || \"pending\",\n expireTime: \"\",\n };\n}\n\n/**\n * List active subscriptions for a target resource.\n */\nexport async function listSpaceSubscriptions(\n spaceName: string,\n auth: WorkspaceEventsAuthOptions,\n): Promise<Array<{ name: string; expireTime: string; eventTypes: string[] }>> {\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n subject: auth.impersonateUser,\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n const response = await workspaceEvents.subscriptions.list({\n filter: `target_resource=\"//chat.googleapis.com/${spaceName}\"`,\n });\n\n return (response.data.subscriptions || []).map((sub) => ({\n name: sub.name || \"\",\n expireTime: sub.expireTime || \"\",\n eventTypes: sub.eventTypes || [],\n }));\n}\n\n/**\n * Delete a Workspace Events subscription.\n */\nexport async function deleteSpaceSubscription(\n subscriptionName: string,\n auth: WorkspaceEventsAuthOptions,\n): Promise<void> {\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n subject: auth.impersonateUser,\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n await workspaceEvents.subscriptions.delete({\n name: subscriptionName,\n });\n}\n\n/**\n * Decode a Pub/Sub push message into a Workspace Event notification.\n *\n * The message uses CloudEvents format where event metadata is in attributes\n * (ce-type, ce-source, ce-subject, ce-time) and the payload is base64 encoded.\n *\n * @example\n * ```typescript\n * // In your /api/webhooks/gchat/pubsub route:\n * const body = await request.json();\n * const event = decodePubSubMessage(body);\n *\n * if (event.eventType === \"google.workspace.chat.message.v1.created\") {\n * // Handle new message\n * console.log(\"New message:\", event.message?.text);\n * }\n * ```\n */\nexport function decodePubSubMessage(\n pushMessage: PubSubPushMessage,\n): WorkspaceEventNotification {\n // Decode the base64 payload\n const data = Buffer.from(pushMessage.message.data, \"base64\").toString(\n \"utf-8\",\n );\n const payload = JSON.parse(data) as {\n message?: GoogleChatMessage;\n reaction?: GoogleChatReaction;\n };\n\n // Extract CloudEvents metadata from attributes\n const attributes = pushMessage.message.attributes || {};\n\n return {\n subscription: pushMessage.subscription,\n targetResource: attributes[\"ce-subject\"] || \"\",\n eventType: attributes[\"ce-type\"] || \"\",\n eventTime: attributes[\"ce-time\"] || pushMessage.message.publishTime,\n message: payload.message,\n reaction: payload.reaction,\n };\n}\n\n/**\n * Verify a Pub/Sub push message is authentic.\n * In production, you should verify the JWT token in the Authorization header.\n *\n * @see https://cloud.google.com/pubsub/docs/authenticate-push-subscriptions\n */\nexport function verifyPubSubRequest(\n request: Request,\n _expectedAudience?: string,\n): boolean {\n // Basic check - Pub/Sub always sends POST with specific content type\n if (request.method !== \"POST\") {\n return false;\n }\n\n const contentType = request.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n return false;\n }\n\n // For full verification, you would:\n // 1. Extract the Bearer token from Authorization header\n // 2. Verify it's a valid Google-signed JWT\n // 3. Check the audience matches your endpoint\n // This requires additional setup - see Google's docs\n\n return true;\n}\n"],"mappings":"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","names":["convertEmojiPlaceholders","google","google","response","convertEmojiPlaceholders"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts","../src/workspace-events.ts"],"sourcesContent":["import type {\n ActionEvent,\n Adapter,\n AdapterPostableMessage,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n RawMessage,\n ReactionEvent,\n StateAdapter,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n isCardElement,\n RateLimitError,\n} from \"chat\";\nimport { type chat_v1, google } from \"googleapis\";\nimport { cardToGoogleCard } from \"./cards\";\nimport { GoogleChatFormatConverter } from \"./markdown\";\nimport {\n createSpaceSubscription,\n decodePubSubMessage,\n listSpaceSubscriptions,\n type PubSubPushMessage,\n type WorkspaceEventNotification,\n type WorkspaceEventsAuthOptions,\n} from \"./workspace-events\";\n\n/** How long before expiry to refresh subscriptions (1 hour) */\nconst SUBSCRIPTION_REFRESH_BUFFER_MS = 60 * 60 * 1000;\n/** TTL for subscription cache entries (25 hours - longer than max subscription lifetime) */\nconst SUBSCRIPTION_CACHE_TTL_MS = 25 * 60 * 60 * 1000;\n/** Key prefix for space subscription cache */\nconst SPACE_SUB_KEY_PREFIX = \"gchat:space-sub:\";\n/** Key prefix for user info cache */\nconst USER_INFO_KEY_PREFIX = \"gchat:user:\";\n/** TTL for user info cache (7 days) */\nconst USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n/** Cached user info */\ninterface CachedUserInfo {\n displayName: string;\n email?: string;\n}\n\n/** Service account credentials for JWT auth */\nexport interface ServiceAccountCredentials {\n client_email: string;\n private_key: string;\n project_id?: string;\n}\n\n/** Base config options shared by all auth methods */\nexport interface GoogleChatAdapterBaseConfig {\n /** Override bot username (optional) */\n userName?: string;\n /**\n * Pub/Sub topic for receiving all messages via Workspace Events.\n * When set, the adapter will automatically create subscriptions when added to a space.\n * Format: \"projects/my-project/topics/my-topic\"\n */\n pubsubTopic?: string;\n /**\n * User email to impersonate for Workspace Events API calls.\n * Required when using domain-wide delegation.\n * This user must have access to the Chat spaces you want to subscribe to.\n */\n impersonateUser?: string;\n /**\n * HTTP endpoint URL for button click actions.\n * Required for HTTP endpoint apps - button clicks will be routed to this URL.\n * Should be the full URL of your webhook endpoint (e.g., \"https://your-app.vercel.app/api/webhooks/gchat\")\n */\n endpointUrl?: string;\n}\n\n/** Config using service account credentials (JSON key file) */\nexport interface GoogleChatAdapterServiceAccountConfig\n extends GoogleChatAdapterBaseConfig {\n /** Service account credentials JSON */\n credentials: ServiceAccountCredentials;\n auth?: never;\n useApplicationDefaultCredentials?: never;\n}\n\n/** Config using Application Default Credentials (ADC) or Workload Identity Federation */\nexport interface GoogleChatAdapterADCConfig\n extends GoogleChatAdapterBaseConfig {\n /**\n * Use Application Default Credentials.\n * Works with:\n * - GOOGLE_APPLICATION_CREDENTIALS env var pointing to a JSON key file\n * - Workload Identity Federation (external_account JSON)\n * - GCE/Cloud Run/Cloud Functions default service account\n * - gcloud auth application-default login (local development)\n */\n useApplicationDefaultCredentials: true;\n credentials?: never;\n auth?: never;\n}\n\n/** Config using a custom auth client */\nexport interface GoogleChatAdapterCustomAuthConfig\n extends GoogleChatAdapterBaseConfig {\n /** Custom auth client (JWT, OAuth2, GoogleAuth, etc.) */\n auth: Parameters<typeof google.chat>[0][\"auth\"];\n credentials?: never;\n useApplicationDefaultCredentials?: never;\n}\n\nexport type GoogleChatAdapterConfig =\n | GoogleChatAdapterServiceAccountConfig\n | GoogleChatAdapterADCConfig\n | GoogleChatAdapterCustomAuthConfig;\n\n/** Google Chat-specific thread ID data */\nexport interface GoogleChatThreadId {\n spaceName: string;\n threadName?: string;\n /** Whether this is a DM space */\n isDM?: boolean;\n}\n\n/** Google Chat message structure */\nexport interface GoogleChatMessage {\n name: string;\n sender: {\n name: string;\n displayName: string;\n type: string;\n email?: string;\n };\n text: string;\n argumentText?: string;\n formattedText?: string;\n thread?: {\n name: string;\n };\n space?: {\n name: string;\n type: string;\n displayName?: string;\n };\n createTime: string;\n annotations?: Array<{\n type: string;\n startIndex?: number;\n length?: number;\n userMention?: {\n user: { name: string; displayName?: string; type: string };\n type: string;\n };\n }>;\n attachment?: Array<{\n name: string;\n contentName: string;\n contentType: string;\n downloadUri?: string;\n }>;\n}\n\n/** Google Chat space structure */\nexport interface GoogleChatSpace {\n name: string;\n type: string;\n displayName?: string;\n spaceThreadingState?: string;\n /** Space type in newer API format: \"SPACE\", \"GROUP_CHAT\", \"DIRECT_MESSAGE\" */\n spaceType?: string;\n /** Whether this is a single-user DM with the bot */\n singleUserBotDm?: boolean;\n}\n\n/** Google Chat user structure */\nexport interface GoogleChatUser {\n name: string;\n displayName: string;\n type: string;\n email?: string;\n}\n\n/**\n * Google Workspace Add-ons event format.\n * This is the format used when configuring the app via Google Cloud Console.\n */\nexport interface GoogleChatEvent {\n commonEventObject?: {\n userLocale?: string;\n hostApp?: string;\n platform?: string;\n /** The function name invoked (for card clicks) */\n invokedFunction?: string;\n /** Parameters passed to the function */\n parameters?: Record<string, string>;\n };\n chat?: {\n user?: GoogleChatUser;\n eventTime?: string;\n messagePayload?: {\n space: GoogleChatSpace;\n message: GoogleChatMessage;\n };\n /** Present when the bot is added to a space */\n addedToSpacePayload?: {\n space: GoogleChatSpace;\n };\n /** Present when the bot is removed from a space */\n removedFromSpacePayload?: {\n space: GoogleChatSpace;\n };\n /** Present when a card button is clicked */\n buttonClickedPayload?: {\n space: GoogleChatSpace;\n message: GoogleChatMessage;\n user: GoogleChatUser;\n };\n };\n}\n\n/** Cached subscription info */\ninterface SpaceSubscriptionInfo {\n subscriptionName: string;\n expireTime: number; // Unix timestamp ms\n}\n\nexport class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown> {\n readonly name = \"gchat\";\n readonly userName: string;\n /** Bot's user ID (e.g., \"users/123...\") - learned from annotations */\n botUserId?: string;\n\n private chatApi: chat_v1.Chat;\n private chat: ChatInstance | null = null;\n private state: StateAdapter | null = null;\n private logger: Logger | null = null;\n private formatConverter = new GoogleChatFormatConverter();\n private pubsubTopic?: string;\n private credentials?: ServiceAccountCredentials;\n private useADC = false;\n /** Custom auth client (e.g., Vercel OIDC) */\n private customAuth?: Parameters<typeof google.chat>[0][\"auth\"];\n /** Auth client for making authenticated requests */\n private authClient!: Parameters<typeof google.chat>[0][\"auth\"];\n /** User email to impersonate for Workspace Events API (domain-wide delegation) */\n private impersonateUser?: string;\n /** In-progress subscription creations to prevent duplicate requests */\n private pendingSubscriptions = new Map<string, Promise<void>>();\n /** Chat API client with impersonation for user-context operations (DMs, etc.) */\n private impersonatedChatApi?: chat_v1.Chat;\n /** HTTP endpoint URL for button click actions */\n private endpointUrl?: string;\n\n constructor(config: GoogleChatAdapterConfig) {\n this.userName = config.userName || \"bot\";\n this.pubsubTopic = config.pubsubTopic;\n this.impersonateUser = config.impersonateUser;\n this.endpointUrl = config.endpointUrl;\n\n let auth: Parameters<typeof google.chat>[0][\"auth\"];\n\n // Scopes needed for full bot functionality including reactions and DMs\n // Note: chat.spaces.create requires domain-wide delegation to work\n const scopes = [\n \"https://www.googleapis.com/auth/chat.bot\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n \"https://www.googleapis.com/auth/chat.messages.reactions.create\",\n \"https://www.googleapis.com/auth/chat.messages.reactions\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n ];\n\n if (\"credentials\" in config && config.credentials) {\n // Service account credentials (JWT)\n this.credentials = config.credentials;\n auth = new google.auth.JWT({\n email: config.credentials.client_email,\n key: config.credentials.private_key,\n scopes,\n });\n } else if (\n \"useApplicationDefaultCredentials\" in config &&\n config.useApplicationDefaultCredentials\n ) {\n // Application Default Credentials (ADC)\n // Works with Workload Identity Federation, GCE metadata, GOOGLE_APPLICATION_CREDENTIALS env var\n this.useADC = true;\n auth = new google.auth.GoogleAuth({\n scopes,\n });\n } else if (\"auth\" in config && config.auth) {\n // Custom auth client provided directly (e.g., Vercel OIDC)\n this.customAuth = config.auth;\n auth = config.auth;\n } else {\n throw new Error(\n \"GoogleChatAdapter requires one of: credentials, useApplicationDefaultCredentials, or auth\",\n );\n }\n\n this.authClient = auth;\n this.chatApi = google.chat({ version: \"v1\", auth });\n\n // Create impersonated Chat API for user-context operations (DMs)\n // Domain-wide delegation requires setting the `subject` claim to the impersonated user\n if (this.impersonateUser) {\n if (this.credentials) {\n const impersonatedAuth = new google.auth.JWT({\n email: this.credentials.client_email,\n key: this.credentials.private_key,\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n subject: this.impersonateUser,\n });\n this.impersonatedChatApi = google.chat({\n version: \"v1\",\n auth: impersonatedAuth,\n });\n } else if (this.useADC) {\n // ADC with impersonation (requires clientOptions.subject support)\n const impersonatedAuth = new google.auth.GoogleAuth({\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces\",\n \"https://www.googleapis.com/auth/chat.spaces.create\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n clientOptions: {\n subject: this.impersonateUser,\n },\n });\n this.impersonatedChatApi = google.chat({\n version: \"v1\",\n auth: impersonatedAuth,\n });\n }\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.state = chat.getState();\n this.logger = chat.getLogger(this.name);\n\n // Restore persisted bot user ID from state (for serverless environments)\n if (!this.botUserId) {\n const savedBotUserId = await this.state.get<string>(\"gchat:botUserId\");\n if (savedBotUserId) {\n this.botUserId = savedBotUserId;\n this.logger?.debug(\"Restored bot user ID from state\", {\n botUserId: this.botUserId,\n });\n }\n }\n }\n\n /**\n * Called when a thread is subscribed to.\n * Ensures the space has a Workspace Events subscription so we receive all messages.\n */\n async onThreadSubscribe(threadId: string): Promise<void> {\n this.logger?.info(\"onThreadSubscribe called\", {\n threadId,\n hasPubsubTopic: !!this.pubsubTopic,\n pubsubTopic: this.pubsubTopic,\n });\n\n if (!this.pubsubTopic) {\n this.logger?.warn(\n \"No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var.\",\n );\n return;\n }\n\n const { spaceName } = this.decodeThreadId(threadId);\n await this.ensureSpaceSubscription(spaceName);\n }\n\n /**\n * Ensure a Workspace Events subscription exists for a space.\n * Creates one if it doesn't exist or is about to expire.\n */\n private async ensureSpaceSubscription(spaceName: string): Promise<void> {\n this.logger?.info(\"ensureSpaceSubscription called\", {\n spaceName,\n hasPubsubTopic: !!this.pubsubTopic,\n hasState: !!this.state,\n hasCredentials: !!this.credentials,\n hasADC: this.useADC,\n });\n\n if (!this.pubsubTopic || !this.state) {\n this.logger?.warn(\"ensureSpaceSubscription skipped - missing config\", {\n hasPubsubTopic: !!this.pubsubTopic,\n hasState: !!this.state,\n });\n return;\n }\n\n const cacheKey = `${SPACE_SUB_KEY_PREFIX}${spaceName}`;\n\n // Check if we already have a valid subscription\n const cached = await this.state.get<SpaceSubscriptionInfo>(cacheKey);\n if (cached) {\n const timeUntilExpiry = cached.expireTime - Date.now();\n if (timeUntilExpiry > SUBSCRIPTION_REFRESH_BUFFER_MS) {\n this.logger?.debug(\"Space subscription still valid\", {\n spaceName,\n expiresIn: Math.round(timeUntilExpiry / 1000 / 60),\n });\n return;\n }\n this.logger?.debug(\"Space subscription expiring soon, will refresh\", {\n spaceName,\n expiresIn: Math.round(timeUntilExpiry / 1000 / 60),\n });\n }\n\n // Check if we're already creating a subscription for this space\n const pending = this.pendingSubscriptions.get(spaceName);\n if (pending) {\n this.logger?.debug(\"Subscription creation already in progress\", {\n spaceName,\n });\n return pending;\n }\n\n // Create the subscription\n const createPromise = this.createSpaceSubscriptionWithCache(\n spaceName,\n cacheKey,\n );\n this.pendingSubscriptions.set(spaceName, createPromise);\n\n try {\n await createPromise;\n } finally {\n this.pendingSubscriptions.delete(spaceName);\n }\n }\n\n /**\n * Create a Workspace Events subscription and cache the result.\n */\n private async createSpaceSubscriptionWithCache(\n spaceName: string,\n cacheKey: string,\n ): Promise<void> {\n const authOptions = this.getAuthOptions();\n this.logger?.info(\"createSpaceSubscriptionWithCache\", {\n spaceName,\n hasAuthOptions: !!authOptions,\n hasCredentials: !!this.credentials,\n hasADC: this.useADC,\n });\n\n if (!authOptions) {\n this.logger?.error(\n \"Cannot create subscription: no auth configured. Use GOOGLE_CHAT_CREDENTIALS, GOOGLE_CHAT_USE_ADC=true, or custom auth.\",\n );\n return;\n }\n\n const pubsubTopic = this.pubsubTopic;\n if (!pubsubTopic) return;\n\n try {\n // First check if a subscription already exists via the API\n const existing = await this.findExistingSubscription(\n spaceName,\n authOptions,\n );\n if (existing) {\n this.logger?.debug(\"Found existing subscription\", {\n spaceName,\n subscriptionName: existing.subscriptionName,\n });\n // Cache it\n if (this.state) {\n await this.state.set<SpaceSubscriptionInfo>(\n cacheKey,\n existing,\n SUBSCRIPTION_CACHE_TTL_MS,\n );\n }\n return;\n }\n\n this.logger?.info(\"Creating Workspace Events subscription\", {\n spaceName,\n pubsubTopic,\n });\n\n const result = await createSpaceSubscription(\n { spaceName, pubsubTopic },\n authOptions,\n );\n\n const subscriptionInfo: SpaceSubscriptionInfo = {\n subscriptionName: result.name,\n expireTime: new Date(result.expireTime).getTime(),\n };\n\n // Cache the subscription info\n if (this.state) {\n await this.state.set<SpaceSubscriptionInfo>(\n cacheKey,\n subscriptionInfo,\n SUBSCRIPTION_CACHE_TTL_MS,\n );\n }\n\n this.logger?.info(\"Workspace Events subscription created\", {\n spaceName,\n subscriptionName: result.name,\n expireTime: result.expireTime,\n });\n } catch (error) {\n this.logger?.error(\"Failed to create Workspace Events subscription\", {\n spaceName,\n error,\n });\n // Don't throw - subscription failure shouldn't break the main flow\n }\n }\n\n /**\n * Check if a subscription already exists for this space.\n */\n private async findExistingSubscription(\n spaceName: string,\n authOptions: WorkspaceEventsAuthOptions,\n ): Promise<SpaceSubscriptionInfo | null> {\n try {\n const subscriptions = await listSpaceSubscriptions(\n spaceName,\n authOptions,\n );\n for (const sub of subscriptions) {\n // Check if this subscription is still valid\n const expireTime = new Date(sub.expireTime).getTime();\n if (expireTime > Date.now() + SUBSCRIPTION_REFRESH_BUFFER_MS) {\n return {\n subscriptionName: sub.name,\n expireTime,\n };\n }\n }\n } catch (error) {\n this.logger?.debug(\"Error checking existing subscriptions\", { error });\n }\n return null;\n }\n\n /**\n * Get auth options for Workspace Events API calls.\n */\n private getAuthOptions(): WorkspaceEventsAuthOptions | null {\n if (this.credentials) {\n return {\n credentials: this.credentials,\n impersonateUser: this.impersonateUser,\n };\n }\n if (this.useADC) {\n return {\n useApplicationDefaultCredentials: true as const,\n impersonateUser: this.impersonateUser,\n };\n }\n if (this.customAuth) {\n return { auth: this.customAuth };\n }\n return null;\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n // Auto-detect endpoint URL from incoming request for button click routing\n // This allows HTTP endpoint apps to work without manual endpointUrl configuration\n if (!this.endpointUrl) {\n try {\n const url = new URL(request.url);\n // Preserve the full URL including query strings\n this.endpointUrl = url.toString();\n this.logger?.debug(\"Auto-detected endpoint URL\", {\n endpointUrl: this.endpointUrl,\n });\n } catch {\n // URL parsing failed, endpointUrl will remain undefined\n }\n }\n\n const body = await request.text();\n this.logger?.debug(\"GChat webhook raw body\", { body });\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Check if this is a Pub/Sub push message (from Workspace Events subscription)\n const maybePubSub = parsed as PubSubPushMessage;\n if (maybePubSub.message?.data && maybePubSub.subscription) {\n return this.handlePubSubMessage(maybePubSub, options);\n }\n\n // Otherwise, treat as a direct Google Chat webhook event\n const event = parsed as GoogleChatEvent;\n\n // Handle ADDED_TO_SPACE - automatically create subscription\n const addedPayload = event.chat?.addedToSpacePayload;\n if (addedPayload) {\n this.logger?.debug(\"Bot added to space\", {\n space: addedPayload.space.name,\n spaceType: addedPayload.space.type,\n });\n this.handleAddedToSpace(addedPayload.space, options);\n }\n\n // Handle REMOVED_FROM_SPACE (for logging)\n const removedPayload = event.chat?.removedFromSpacePayload;\n if (removedPayload) {\n this.logger?.debug(\"Bot removed from space\", {\n space: removedPayload.space.name,\n });\n }\n\n // Handle card button clicks\n const buttonClickedPayload = event.chat?.buttonClickedPayload;\n const invokedFunction = event.commonEventObject?.invokedFunction;\n if (buttonClickedPayload || invokedFunction) {\n this.handleCardClick(event, options);\n // For HTTP endpoint apps (Workspace Add-ons), return empty JSON to acknowledge.\n // The RenderActions format expects cards in google.apps.card.v1 format,\n // actionResponse is for the older Google Chat API format.\n // Returning {} acknowledges the action without changing the card.\n return new Response(JSON.stringify({}), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n // Check for message payload in the Add-ons format\n const messagePayload = event.chat?.messagePayload;\n if (messagePayload) {\n this.logger?.debug(\"message event\", {\n space: messagePayload.space.name,\n sender: messagePayload.message.sender?.displayName,\n text: messagePayload.message.text?.slice(0, 50),\n });\n this.handleMessageEvent(event, options);\n } else if (!addedPayload && !removedPayload) {\n this.logger?.debug(\"Non-message event received\", {\n hasChat: !!event.chat,\n hasCommonEventObject: !!event.commonEventObject,\n });\n }\n\n // Google Chat expects an empty response or a message response\n return new Response(JSON.stringify({}), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n /**\n * Handle Pub/Sub push messages from Workspace Events subscriptions.\n * These contain all messages in a space, not just @mentions.\n */\n private handlePubSubMessage(\n pushMessage: PubSubPushMessage,\n options?: WebhookOptions,\n ): Response {\n // Early filter: Check event type BEFORE base64 decoding to save CPU\n // The ce-type attribute is available in message.attributes\n const eventType = pushMessage.message?.attributes?.[\"ce-type\"];\n const allowedEventTypes = [\n \"google.workspace.chat.message.v1.created\",\n \"google.workspace.chat.reaction.v1.created\",\n \"google.workspace.chat.reaction.v1.deleted\",\n ];\n if (eventType && !allowedEventTypes.includes(eventType)) {\n this.logger?.debug(\"Skipping unsupported Pub/Sub event\", { eventType });\n return new Response(JSON.stringify({ success: true }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n try {\n const notification = decodePubSubMessage(pushMessage);\n this.logger?.debug(\"Pub/Sub notification decoded\", {\n eventType: notification.eventType,\n messageId: notification.message?.name,\n reactionName: notification.reaction?.name,\n });\n\n // Handle message.created events\n if (notification.message) {\n this.handlePubSubMessageEvent(notification, options);\n }\n\n // Handle reaction events\n if (notification.reaction) {\n this.handlePubSubReactionEvent(notification, options);\n }\n\n // Acknowledge the message\n return new Response(JSON.stringify({ success: true }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n this.logger?.error(\"Error processing Pub/Sub message\", { error });\n // Return 200 to avoid retries for malformed messages\n return new Response(JSON.stringify({ error: \"Processing failed\" }), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n }\n\n /**\n * Handle message events received via Pub/Sub (Workspace Events).\n */\n private handlePubSubMessageEvent(\n notification: WorkspaceEventNotification,\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !notification.message) {\n return;\n }\n\n const message = notification.message;\n // Extract space name from targetResource: \"//chat.googleapis.com/spaces/AAAA\"\n const spaceName = notification.targetResource?.replace(\n \"//chat.googleapis.com/\",\n \"\",\n );\n const threadName = message.thread?.name || message.name;\n const threadId = this.encodeThreadId({\n spaceName: spaceName || message.space?.name || \"\",\n threadName,\n });\n\n // Refresh subscription if needed (runs in background)\n const resolvedSpaceName = spaceName || message.space?.name;\n if (resolvedSpaceName && options?.waitUntil) {\n options.waitUntil(\n this.ensureSpaceSubscription(resolvedSpaceName).catch((err) => {\n this.logger?.debug(\"Subscription refresh failed\", { error: err });\n }),\n );\n }\n\n // Let Chat class handle async processing and waitUntil\n // Use factory function since parsePubSubMessage is async (user display name lookup)\n this.chat.processMessage(\n this,\n threadId,\n () => this.parsePubSubMessage(notification, threadId),\n options,\n );\n }\n\n /**\n * Handle reaction events received via Pub/Sub (Workspace Events).\n * Fetches the message to get thread context for proper reply threading.\n */\n private handlePubSubReactionEvent(\n notification: WorkspaceEventNotification,\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !notification.reaction) {\n return;\n }\n\n const reaction = notification.reaction;\n const rawEmoji = reaction.emoji?.unicode || \"\";\n const normalizedEmoji = defaultEmojiResolver.fromGChat(rawEmoji);\n\n // Extract message name from reaction name\n // Format: spaces/{space}/messages/{message}/reactions/{reaction}\n const reactionName = reaction.name || \"\";\n const messageNameMatch = reactionName.match(\n /(spaces\\/[^/]+\\/messages\\/[^/]+)/,\n );\n const messageName = messageNameMatch ? messageNameMatch[1] : \"\";\n\n // Extract space name from targetResource\n const spaceName = notification.targetResource?.replace(\n \"//chat.googleapis.com/\",\n \"\",\n );\n\n // Check if reaction is from this bot\n const isMe =\n this.botUserId !== undefined && reaction.user?.name === this.botUserId;\n\n // Determine if this is an add or remove\n const added = notification.eventType.includes(\"created\");\n\n // We need to fetch the message to get its thread context\n // This is done lazily when the reaction is processed\n const chat = this.chat;\n const buildReactionEvent = async (): Promise<\n Omit<ReactionEvent, \"adapter\" | \"thread\"> & { adapter: GoogleChatAdapter }\n > => {\n let threadId: string;\n\n // Fetch the message to get its thread name\n if (messageName) {\n try {\n const messageResponse = await this.chatApi.spaces.messages.get({\n name: messageName,\n });\n const threadName = messageResponse.data.thread?.name;\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n threadName: threadName ?? undefined,\n });\n this.logger?.debug(\"Fetched thread context for reaction\", {\n messageName,\n threadName,\n threadId,\n });\n } catch (error) {\n this.logger?.warn(\"Failed to fetch message for thread context\", {\n messageName,\n error,\n });\n // Fall back to space-only thread ID\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n });\n }\n } else {\n threadId = this.encodeThreadId({\n spaceName: spaceName || \"\",\n });\n }\n\n return {\n emoji: normalizedEmoji,\n rawEmoji,\n added,\n user: {\n userId: reaction.user?.name || \"unknown\",\n userName: reaction.user?.displayName || \"unknown\",\n fullName: reaction.user?.displayName || \"unknown\",\n isBot: reaction.user?.type === \"BOT\",\n isMe,\n },\n messageId: messageName,\n threadId,\n raw: notification,\n adapter: this,\n };\n };\n\n // Process reaction with lazy thread resolution\n const processTask = buildReactionEvent().then((reactionEvent) => {\n chat.processReaction(reactionEvent, options);\n });\n\n if (options?.waitUntil) {\n options.waitUntil(processTask);\n }\n }\n\n /**\n * Parse a Pub/Sub message into the standard Message format.\n * Resolves user display names from cache since Pub/Sub messages don't include them.\n */\n private async parsePubSubMessage(\n notification: WorkspaceEventNotification,\n threadId: string,\n ): Promise<Message<unknown>> {\n const message = notification.message;\n if (!message) {\n throw new Error(\"PubSub notification missing message\");\n }\n const text = this.normalizeBotMentions(message);\n const isBot = message.sender?.type === \"BOT\";\n const isMe = this.isMessageFromSelf(message);\n\n // Pub/Sub messages don't include displayName - resolve from cache\n const userId = message.sender?.name || \"unknown\";\n const displayName = await this.resolveUserDisplayName(\n userId,\n message.sender?.displayName,\n );\n\n const parsedMessage: Message<unknown> = {\n id: message.name,\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: notification,\n author: {\n userId,\n userName: displayName,\n fullName: displayName,\n isBot,\n isMe,\n },\n metadata: {\n dateSent: new Date(message.createTime),\n edited: false,\n },\n attachments: (message.attachment || []).map((att) =>\n this.createAttachment(att),\n ),\n };\n\n this.logger?.debug(\"Pub/Sub parsed message\", {\n threadId,\n messageId: parsedMessage.id,\n text: parsedMessage.text,\n author: parsedMessage.author.fullName,\n isBot: parsedMessage.author.isBot,\n isMe: parsedMessage.author.isMe,\n });\n\n return parsedMessage;\n }\n\n /**\n * Handle bot being added to a space - create Workspace Events subscription.\n */\n private handleAddedToSpace(\n space: GoogleChatSpace,\n options?: WebhookOptions,\n ): void {\n const subscribeTask = this.ensureSpaceSubscription(space.name);\n\n if (options?.waitUntil) {\n options.waitUntil(subscribeTask);\n }\n }\n\n /**\n * Handle card button clicks.\n * For HTTP endpoint apps, the actionId is passed via parameters (since function is the URL).\n * For other deployments, actionId may be in invokedFunction.\n */\n private handleCardClick(\n event: GoogleChatEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring card click\");\n return;\n }\n\n const buttonPayload = event.chat?.buttonClickedPayload;\n const commonEvent = event.commonEventObject;\n\n // Get action ID - for HTTP endpoints it's in parameters.actionId,\n // for other deployments it may be in invokedFunction\n const actionId =\n commonEvent?.parameters?.actionId || commonEvent?.invokedFunction;\n if (!actionId) {\n this.logger?.debug(\"Card click missing actionId\", {\n parameters: commonEvent?.parameters,\n invokedFunction: commonEvent?.invokedFunction,\n });\n return;\n }\n\n // Get value from parameters\n const value = commonEvent?.parameters?.value;\n\n // Get space and message info from buttonClickedPayload\n const space = buttonPayload?.space;\n const message = buttonPayload?.message;\n const user = buttonPayload?.user || event.chat?.user;\n\n if (!space) {\n this.logger?.warn(\"Card click missing space info\");\n return;\n }\n\n const threadName = message?.thread?.name || message?.name;\n const threadId = this.encodeThreadId({\n spaceName: space.name,\n threadName,\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & {\n adapter: GoogleChatAdapter;\n } = {\n actionId,\n value,\n user: {\n userId: user?.name || \"unknown\",\n userName: user?.displayName || \"unknown\",\n fullName: user?.displayName || \"unknown\",\n isBot: user?.type === \"BOT\",\n isMe: false,\n },\n messageId: message?.name || \"\",\n threadId,\n adapter: this,\n raw: event,\n };\n\n this.logger?.debug(\"Processing GChat card click\", {\n actionId,\n value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n\n /**\n * Handle direct webhook message events (Add-ons format).\n */\n private handleMessageEvent(\n event: GoogleChatEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n const messagePayload = event.chat?.messagePayload;\n if (!messagePayload) {\n this.logger?.debug(\"Ignoring event without messagePayload\");\n return;\n }\n\n const message = messagePayload.message;\n // For DMs, use space-only thread ID so all messages in the DM\n // match the DM subscription created by openDM(). This treats the entire DM\n // conversation as a single \"thread\" for subscription purposes.\n const isDM =\n messagePayload.space.type === \"DM\" ||\n messagePayload.space.spaceType === \"DIRECT_MESSAGE\";\n const threadName = isDM ? undefined : message.thread?.name || message.name;\n const threadId = this.encodeThreadId({\n spaceName: messagePayload.space.name,\n threadName,\n isDM,\n });\n\n // Let Chat class handle async processing and waitUntil\n this.chat.processMessage(\n this,\n threadId,\n this.parseGoogleChatMessage(event, threadId),\n options,\n );\n }\n\n private parseGoogleChatMessage(\n event: GoogleChatEvent,\n threadId: string,\n ): Message<unknown> {\n const message = event.chat?.messagePayload?.message;\n if (!message) {\n throw new Error(\"Event has no message payload\");\n }\n\n // Normalize bot mentions: replace @BotDisplayName with @{userName}\n // so the Chat SDK's mention detection works properly\n const text = this.normalizeBotMentions(message);\n\n const isBot = message.sender?.type === \"BOT\";\n const isMe = this.isMessageFromSelf(message);\n\n // Cache user info for future Pub/Sub messages (which don't include displayName)\n const userId = message.sender?.name || \"unknown\";\n const displayName = message.sender?.displayName || \"unknown\";\n if (userId !== \"unknown\" && displayName !== \"unknown\") {\n this.cacheUserInfo(userId, displayName, message.sender?.email).catch(\n () => {},\n );\n }\n\n return {\n id: message.name,\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId,\n userName: displayName,\n fullName: displayName,\n isBot,\n isMe,\n },\n metadata: {\n dateSent: new Date(message.createTime),\n edited: false,\n },\n attachments: (message.attachment || []).map((att) =>\n this.createAttachment(att),\n ),\n };\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { spaceName, threadName } = this.decodeThreadId(threadId);\n\n try {\n // Check for files - currently not implemented for GChat\n const files = this.extractFiles(message);\n if (files.length > 0) {\n this.logger?.warn(\n \"File uploads are not yet supported for Google Chat. Files will be ignored.\",\n { fileCount: files.length },\n );\n // TODO: Implement using Google Chat media.upload API\n }\n\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Google Chat Card\n // cardId is required for interactive cards (button clicks)\n // endpointUrl is required for HTTP endpoint apps to route button clicks\n const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n const googleCard = cardToGoogleCard(card, {\n cardId,\n endpointUrl: this.endpointUrl,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create (card)\", {\n spaceName,\n threadName,\n googleCard: JSON.stringify(googleCard),\n });\n\n const response = await this.chatApi.spaces.messages.create({\n parent: spaceName,\n messageReplyOption: threadName\n ? \"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD\"\n : undefined,\n requestBody: {\n // Don't include text - GChat shows both text and card if text is present\n cardsV2: [googleCard],\n thread: threadName ? { name: threadName } : undefined,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"gchat\",\n );\n\n this.logger?.debug(\"GChat API: spaces.messages.create\", {\n spaceName,\n threadName,\n textLength: text.length,\n });\n\n const response = await this.chatApi.spaces.messages.create({\n parent: spaceName,\n // Required to reply in an existing thread\n messageReplyOption: threadName\n ? \"REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD\"\n : undefined,\n requestBody: {\n text,\n thread: threadName ? { name: threadName } : undefined,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.create response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"postMessage\");\n }\n }\n\n /**\n * Extract card element from a message if present.\n */\n private extractCard(\n message: AdapterPostableMessage,\n ): import(\"chat\").CardElement | null {\n if (isCardElement(message)) {\n return message;\n }\n if (typeof message === \"object\" && message !== null && \"card\" in message) {\n return message.card;\n }\n return null;\n }\n\n /**\n * Extract files from a message if present.\n */\n private extractFiles(message: AdapterPostableMessage): FileUpload[] {\n if (typeof message === \"object\" && message !== null && \"files\" in message) {\n return (message as { files?: FileUpload[] }).files ?? [];\n }\n return [];\n }\n\n /**\n * Create an Attachment object from a Google Chat attachment.\n */\n private createAttachment(att: {\n contentType?: string | null;\n downloadUri?: string | null;\n contentName?: string | null;\n thumbnailUri?: string | null;\n }): Attachment {\n const url = att.downloadUri || undefined;\n const authClient = this.authClient;\n\n // Determine type based on contentType\n let type: Attachment[\"type\"] = \"file\";\n if (att.contentType?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (att.contentType?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (att.contentType?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n // Capture auth client for use in fetchData closure\n const auth = authClient;\n\n return {\n type,\n url,\n name: att.contentName || undefined,\n mimeType: att.contentType || undefined,\n fetchData: url\n ? async () => {\n // Get access token for authenticated download\n if (typeof auth === \"string\" || !auth) {\n throw new Error(\"Cannot fetch file: no auth client configured\");\n }\n const tokenResult = await auth.getAccessToken();\n const token =\n typeof tokenResult === \"string\"\n ? tokenResult\n : tokenResult?.token;\n if (!token) {\n throw new Error(\"Failed to get access token\");\n }\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n try {\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Google Chat Card\n // cardId is required for interactive cards (button clicks)\n // endpointUrl is required for HTTP endpoint apps to route button clicks\n const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n const googleCard = cardToGoogleCard(card, {\n cardId,\n endpointUrl: this.endpointUrl,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update (card)\", {\n messageId,\n cardId,\n });\n\n const response = await this.chatApi.spaces.messages.update({\n name: messageId,\n updateMask: \"cardsV2\",\n requestBody: {\n // Don't include text - GChat shows both text and card if text is present\n cardsV2: [googleCard],\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"gchat\",\n );\n\n this.logger?.debug(\"GChat API: spaces.messages.update\", {\n messageId,\n textLength: text.length,\n });\n\n const response = await this.chatApi.spaces.messages.update({\n name: messageId,\n updateMask: \"text\",\n requestBody: {\n text,\n },\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.update response\", {\n messageName: response.data.name,\n });\n\n return {\n id: response.data.name || \"\",\n threadId,\n raw: response.data,\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"editMessage\");\n }\n }\n\n async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n try {\n this.logger?.debug(\"GChat API: spaces.messages.delete\", { messageId });\n\n await this.chatApi.spaces.messages.delete({\n name: messageId,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.delete response\", {\n ok: true,\n });\n } catch (error) {\n this.handleGoogleChatError(error, \"deleteMessage\");\n }\n }\n\n async addReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n // Convert emoji (EmojiValue or string) to GChat unicode format\n const gchatEmoji = defaultEmojiResolver.toGChat(emoji);\n\n try {\n this.logger?.debug(\"GChat API: spaces.messages.reactions.create\", {\n messageId,\n emoji: gchatEmoji,\n });\n\n await this.chatApi.spaces.messages.reactions.create({\n parent: messageId,\n requestBody: {\n emoji: { unicode: gchatEmoji },\n },\n });\n\n this.logger?.debug(\n \"GChat API: spaces.messages.reactions.create response\",\n {\n ok: true,\n },\n );\n } catch (error) {\n this.handleGoogleChatError(error, \"addReaction\");\n }\n }\n\n async removeReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n // Convert emoji (EmojiValue or string) to GChat unicode format\n const gchatEmoji = defaultEmojiResolver.toGChat(emoji);\n\n try {\n // Google Chat requires the reaction resource name to delete it.\n // We need to list reactions and find the one with matching emoji.\n this.logger?.debug(\"GChat API: spaces.messages.reactions.list\", {\n messageId,\n });\n\n const response = await this.chatApi.spaces.messages.reactions.list({\n parent: messageId,\n });\n\n this.logger?.debug(\"GChat API: spaces.messages.reactions.list response\", {\n reactionCount: response.data.reactions?.length || 0,\n });\n\n const reaction = response.data.reactions?.find(\n (r) => r.emoji?.unicode === gchatEmoji,\n );\n\n if (!reaction?.name) {\n this.logger?.debug(\"Reaction not found to remove\", {\n messageId,\n emoji: gchatEmoji,\n });\n return;\n }\n\n this.logger?.debug(\"GChat API: spaces.messages.reactions.delete\", {\n reactionName: reaction.name,\n });\n\n await this.chatApi.spaces.messages.reactions.delete({\n name: reaction.name,\n });\n\n this.logger?.debug(\n \"GChat API: spaces.messages.reactions.delete response\",\n {\n ok: true,\n },\n );\n } catch (error) {\n this.handleGoogleChatError(error, \"removeReaction\");\n }\n }\n\n async startTyping(_threadId: string): Promise<void> {\n // Google Chat doesn't have a typing indicator API for bots\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n *\n * For Google Chat, this first tries to find an existing DM space with the user.\n * If no DM exists, it creates one using spaces.setup.\n *\n * @param userId - The user's resource name (e.g., \"users/123456\")\n */\n async openDM(userId: string): Promise<string> {\n try {\n // First, try to find an existing DM space with this user\n // This works with the bot's own credentials (no impersonation needed)\n this.logger?.debug(\"GChat API: spaces.findDirectMessage\", { userId });\n\n const findResponse = await this.chatApi.spaces.findDirectMessage({\n name: userId,\n });\n\n if (findResponse.data.name) {\n this.logger?.debug(\"GChat API: Found existing DM space\", {\n spaceName: findResponse.data.name,\n });\n return this.encodeThreadId({\n spaceName: findResponse.data.name,\n isDM: true,\n });\n }\n } catch (error) {\n // 404 means no DM exists yet - we'll try to create one\n const gError = error as { code?: number };\n if (gError.code !== 404) {\n this.logger?.debug(\"GChat API: findDirectMessage failed\", { error });\n }\n }\n\n // No existing DM found - try to create one\n // Use impersonated API if available (required for creating new DMs)\n const chatApi = this.impersonatedChatApi || this.chatApi;\n\n if (!this.impersonatedChatApi) {\n this.logger?.warn(\n \"openDM: No existing DM found and no impersonation configured. \" +\n \"Creating new DMs requires domain-wide delegation. \" +\n \"Set 'impersonateUser' in adapter config.\",\n );\n }\n\n try {\n this.logger?.debug(\"GChat API: spaces.setup (DM)\", {\n userId,\n hasImpersonation: !!this.impersonatedChatApi,\n impersonateUser: this.impersonateUser,\n });\n\n // Create a DM space between the impersonated user and the target user\n // Don't use singleUserBotDm - that's for DMs with the bot itself\n const response = await chatApi.spaces.setup({\n requestBody: {\n space: {\n spaceType: \"DIRECT_MESSAGE\",\n },\n memberships: [\n {\n member: {\n name: userId,\n type: \"HUMAN\",\n },\n },\n ],\n },\n });\n\n const spaceName = response.data.name;\n\n if (!spaceName) {\n throw new Error(\"Failed to create DM - no space name returned\");\n }\n\n this.logger?.debug(\"GChat API: spaces.setup response\", { spaceName });\n\n return this.encodeThreadId({ spaceName, isDM: true });\n } catch (error) {\n this.handleGoogleChatError(error, \"openDM\");\n }\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<Message<unknown>[]> {\n const { spaceName } = this.decodeThreadId(threadId);\n\n // Use impersonated client if available (has better permissions for listing messages)\n const api = this.impersonatedChatApi || this.chatApi;\n\n try {\n this.logger?.debug(\"GChat API: spaces.messages.list\", {\n spaceName,\n pageSize: options.limit || 100,\n impersonated: !!this.impersonatedChatApi,\n });\n\n const response = await api.spaces.messages.list({\n parent: spaceName,\n pageSize: options.limit || 100,\n pageToken: options.before,\n });\n\n const messages = response.data.messages || [];\n\n this.logger?.debug(\"GChat API: spaces.messages.list response\", {\n messageCount: messages.length,\n });\n\n return messages.map((msg) => {\n const msgThreadId = this.encodeThreadId({\n spaceName,\n threadName: msg.thread?.name ?? undefined,\n });\n const msgIsBot = msg.sender?.type === \"BOT\";\n return {\n id: msg.name || \"\",\n threadId: msgThreadId,\n text: this.formatConverter.extractPlainText(msg.text || \"\"),\n formatted: this.formatConverter.toAst(msg.text || \"\"),\n raw: msg,\n author: {\n userId: msg.sender?.name || \"unknown\",\n userName: msg.sender?.displayName || \"unknown\",\n fullName: msg.sender?.displayName || \"unknown\",\n isBot: msgIsBot,\n isMe: msgIsBot,\n },\n metadata: {\n dateSent: msg.createTime ? new Date(msg.createTime) : new Date(),\n edited: false,\n },\n attachments: [],\n };\n });\n } catch (error) {\n this.handleGoogleChatError(error, \"fetchMessages\");\n }\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { spaceName } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"GChat API: spaces.get\", { spaceName });\n\n const response = await this.chatApi.spaces.get({ name: spaceName });\n\n this.logger?.debug(\"GChat API: spaces.get response\", {\n displayName: response.data.displayName,\n });\n\n return {\n id: threadId,\n channelId: spaceName,\n channelName: response.data.displayName ?? undefined,\n metadata: {\n space: response.data,\n },\n };\n } catch (error) {\n this.handleGoogleChatError(error, \"fetchThread\");\n }\n }\n\n encodeThreadId(platformData: GoogleChatThreadId): string {\n const threadPart = platformData.threadName\n ? `:${Buffer.from(platformData.threadName).toString(\"base64url\")}`\n : \"\";\n // Add :dm suffix for DM threads to enable isDM() detection\n const dmPart = platformData.isDM ? \":dm\" : \"\";\n return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Checks for the :dm marker in the thread ID which is set when\n * processing DM messages or opening DMs.\n */\n isDM(threadId: string): boolean {\n // Check for explicit :dm marker in thread ID\n return threadId.endsWith(\":dm\");\n }\n\n decodeThreadId(threadId: string): GoogleChatThreadId {\n // Remove :dm suffix if present\n const isDM = threadId.endsWith(\":dm\");\n const cleanId = isDM ? threadId.slice(0, -3) : threadId;\n\n const parts = cleanId.split(\":\");\n if (parts.length < 2 || parts[0] !== \"gchat\") {\n throw new Error(`Invalid Google Chat thread ID: ${threadId}`);\n }\n\n const spaceName = parts[1] as string;\n const threadName = parts[2]\n ? Buffer.from(parts[2], \"base64url\").toString(\"utf-8\")\n : undefined;\n\n return { spaceName, threadName, isDM };\n }\n\n parseMessage(raw: unknown): Message<unknown> {\n const event = raw as GoogleChatEvent;\n const messagePayload = event.chat?.messagePayload;\n if (!messagePayload) {\n throw new Error(\"Cannot parse non-message event\");\n }\n const threadName =\n messagePayload.message.thread?.name || messagePayload.message.name;\n const threadId = this.encodeThreadId({\n spaceName: messagePayload.space.name,\n threadName,\n });\n return this.parseGoogleChatMessage(event, threadId);\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Normalize bot mentions in message text.\n * Google Chat uses the bot's display name (e.g., \"@Chat SDK Demo\") but the\n * Chat SDK expects \"@{userName}\" format. This method replaces bot mentions\n * with the adapter's userName so mention detection works properly.\n * Also learns the bot's user ID from annotations for isMe detection.\n */\n private normalizeBotMentions(message: GoogleChatMessage): string {\n let text = message.text || \"\";\n\n // Find bot mentions in annotations and replace with @{userName}\n const annotations = message.annotations || [];\n for (const annotation of annotations) {\n if (\n annotation.type === \"USER_MENTION\" &&\n annotation.userMention?.user?.type === \"BOT\"\n ) {\n const botUser = annotation.userMention.user;\n const botDisplayName = botUser.displayName;\n\n // Learn our bot's user ID from mentions and persist to state\n if (botUser.name && !this.botUserId) {\n this.botUserId = botUser.name;\n this.logger?.info(\"Learned bot user ID from mention\", {\n botUserId: this.botUserId,\n });\n // Persist to state for serverless environments\n this.state\n ?.set(\"gchat:botUserId\", this.botUserId)\n .catch((err) =>\n this.logger?.debug(\"Failed to persist botUserId\", { error: err }),\n );\n }\n\n // Replace the bot mention with @{userName}\n // Pub/Sub messages don't include displayName, so use startIndex/length\n if (\n annotation.startIndex !== undefined &&\n annotation.length !== undefined\n ) {\n const startIndex = annotation.startIndex;\n const length = annotation.length;\n const mentionText = text.slice(startIndex, startIndex + length);\n text =\n text.slice(0, startIndex) +\n `@${this.userName}` +\n text.slice(startIndex + length);\n this.logger?.debug(\"Normalized bot mention\", {\n original: mentionText,\n replacement: `@${this.userName}`,\n });\n } else if (botDisplayName) {\n // Fallback: use displayName if available (direct webhook)\n const mentionText = `@${botDisplayName}`;\n text = text.replace(mentionText, `@${this.userName}`);\n }\n }\n }\n\n return text;\n }\n\n /**\n * Check if a message is from this bot.\n *\n * Bot user ID is learned dynamically from message annotations when the bot\n * is @mentioned. Until we learn the ID, we cannot reliably determine isMe.\n *\n * This is safer than the previous approach of assuming all BOT messages are\n * from self, which would incorrectly filter messages from other bots in\n * multi-bot spaces (especially via Pub/Sub).\n */\n private isMessageFromSelf(message: GoogleChatMessage): boolean {\n const senderId = message.sender?.name;\n\n // Use exact match when we know our bot ID\n if (this.botUserId && senderId) {\n return senderId === this.botUserId;\n }\n\n // If we don't know our bot ID yet, we can't reliably determine isMe.\n // Log a debug message and return false - better to process a self-message\n // than to incorrectly filter out messages from other bots.\n if (!this.botUserId && message.sender?.type === \"BOT\") {\n this.logger?.debug(\n \"Cannot determine isMe - bot user ID not yet learned. \" +\n \"Bot ID is learned from @mentions. Assuming message is not from self.\",\n { senderId },\n );\n }\n\n return false;\n }\n\n /**\n * Cache user info for later lookup (e.g., when processing Pub/Sub messages).\n */\n private async cacheUserInfo(\n userId: string,\n displayName: string,\n email?: string,\n ): Promise<void> {\n if (!this.state || !displayName || displayName === \"unknown\") return;\n\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n await this.state.set<CachedUserInfo>(\n cacheKey,\n { displayName, email },\n USER_INFO_CACHE_TTL_MS,\n );\n }\n\n /**\n * Get cached user info.\n */\n private async getCachedUserInfo(\n userId: string,\n ): Promise<CachedUserInfo | null> {\n if (!this.state) return null;\n\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n return this.state.get<CachedUserInfo>(cacheKey);\n }\n\n /**\n * Resolve user display name, using cache if available.\n */\n private async resolveUserDisplayName(\n userId: string,\n providedDisplayName?: string,\n ): Promise<string> {\n // If display name is provided and not \"unknown\", use it\n if (providedDisplayName && providedDisplayName !== \"unknown\") {\n // Also cache it for future use\n this.cacheUserInfo(userId, providedDisplayName).catch(() => {});\n return providedDisplayName;\n }\n\n // Try to get from cache\n const cached = await this.getCachedUserInfo(userId);\n if (cached?.displayName) {\n return cached.displayName;\n }\n\n // Fall back to extracting name from userId (e.g., \"users/123\" -> \"User 123\")\n return userId.replace(\"users/\", \"User \");\n }\n\n private handleGoogleChatError(error: unknown, context?: string): never {\n const gError = error as {\n code?: number;\n message?: string;\n errors?: unknown;\n };\n\n // Log the error at error level for visibility\n this.logger?.error(`GChat API error${context ? ` (${context})` : \"\"}`, {\n code: gError.code,\n message: gError.message,\n errors: gError.errors,\n error,\n });\n\n if (gError.code === 429) {\n throw new RateLimitError(\n \"Google Chat rate limit exceeded\",\n undefined,\n error,\n );\n }\n\n throw error;\n }\n}\n\nexport function createGoogleChatAdapter(\n config: GoogleChatAdapterConfig,\n): GoogleChatAdapter {\n return new GoogleChatAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToFallbackText, cardToGoogleCard } from \"./cards\";\nexport { GoogleChatFormatConverter } from \"./markdown\";\n\nexport {\n type CreateSpaceSubscriptionOptions,\n createSpaceSubscription,\n decodePubSubMessage,\n deleteSpaceSubscription,\n listSpaceSubscriptions,\n type PubSubPushMessage,\n type SpaceSubscriptionResult,\n verifyPubSubRequest,\n type WorkspaceEventNotification,\n type WorkspaceEventsAuthOptions,\n} from \"./workspace-events\";\n","/**\n * Google Chat Card converter for cross-platform cards.\n *\n * Converts CardElement to Google Chat Card v2 format.\n * @see https://developers.google.com/chat/api/reference/rest/v1/cards\n */\n\nimport {\n type ActionsElement,\n type ButtonElement,\n type CardChild,\n type CardElement,\n convertEmojiPlaceholders,\n type DividerElement,\n type FieldsElement,\n type ImageElement,\n type SectionElement,\n type TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to GChat format (Unicode).\n */\nfunction convertEmoji(text: string): string {\n return convertEmojiPlaceholders(text, \"gchat\");\n}\n\n// Google Chat Card v2 types (simplified)\nexport interface GoogleChatCard {\n cardId?: string;\n card: {\n header?: GoogleChatCardHeader;\n sections: GoogleChatCardSection[];\n };\n}\n\nexport interface GoogleChatCardHeader {\n title: string;\n subtitle?: string;\n imageUrl?: string;\n imageType?: \"CIRCLE\" | \"SQUARE\";\n}\n\nexport interface GoogleChatCardSection {\n header?: string;\n widgets: GoogleChatWidget[];\n collapsible?: boolean;\n}\n\nexport interface GoogleChatWidget {\n textParagraph?: { text: string };\n image?: { imageUrl: string; altText?: string };\n decoratedText?: {\n topLabel?: string;\n text: string;\n bottomLabel?: string;\n startIcon?: { knownIcon?: string };\n };\n buttonList?: { buttons: GoogleChatButton[] };\n divider?: Record<string, never>;\n}\n\nexport interface GoogleChatButton {\n text: string;\n onClick: {\n action: {\n function: string;\n parameters: Array<{ key: string; value: string }>;\n };\n };\n color?: { red: number; green: number; blue: number };\n}\n\n/**\n * Options for card conversion.\n */\nexport interface CardConversionOptions {\n /** Unique card ID for interactive cards */\n cardId?: string;\n /**\n * HTTP endpoint URL for button actions.\n * Required for HTTP endpoint apps - button clicks will be routed to this URL.\n */\n endpointUrl?: string;\n}\n\n/**\n * Convert a CardElement to Google Chat Card v2 format.\n */\nexport function cardToGoogleCard(\n card: CardElement,\n options?: CardConversionOptions | string,\n): GoogleChatCard {\n // Support legacy signature where second arg is cardId string\n const opts: CardConversionOptions =\n typeof options === \"string\" ? { cardId: options } : options || {};\n\n const sections: GoogleChatCardSection[] = [];\n\n // Build header\n let header: GoogleChatCardHeader | undefined;\n if (card.title || card.subtitle || card.imageUrl) {\n header = {\n title: convertEmoji(card.title || \"\"),\n };\n if (card.subtitle) {\n header.subtitle = convertEmoji(card.subtitle);\n }\n if (card.imageUrl) {\n header.imageUrl = card.imageUrl;\n header.imageType = \"SQUARE\";\n }\n }\n\n // Group children into sections\n // GChat cards require widgets to be inside sections\n let currentWidgets: GoogleChatWidget[] = [];\n\n for (const child of card.children) {\n if (child.type === \"section\") {\n // If we have pending widgets, flush them to a section\n if (currentWidgets.length > 0) {\n sections.push({ widgets: currentWidgets });\n currentWidgets = [];\n }\n // Convert section as its own section\n const sectionWidgets = convertSectionToWidgets(child, opts.endpointUrl);\n sections.push({ widgets: sectionWidgets });\n } else {\n // Add to current widgets\n const widgets = convertChildToWidgets(child, opts.endpointUrl);\n currentWidgets.push(...widgets);\n }\n }\n\n // Flush remaining widgets\n if (currentWidgets.length > 0) {\n sections.push({ widgets: currentWidgets });\n }\n\n // GChat requires at least one section with at least one widget\n if (sections.length === 0) {\n sections.push({\n widgets: [{ textParagraph: { text: \"\" } }],\n });\n }\n\n const googleCard: GoogleChatCard = {\n card: {\n sections,\n },\n };\n\n if (header) {\n googleCard.card.header = header;\n }\n\n if (opts.cardId) {\n googleCard.cardId = opts.cardId;\n }\n\n return googleCard;\n}\n\n/**\n * Convert a card child element to Google Chat widgets.\n */\nfunction convertChildToWidgets(\n child: CardChild,\n endpointUrl?: string,\n): GoogleChatWidget[] {\n switch (child.type) {\n case \"text\":\n return [convertTextToWidget(child)];\n case \"image\":\n return [convertImageToWidget(child)];\n case \"divider\":\n return [convertDividerToWidget(child)];\n case \"actions\":\n return [convertActionsToWidget(child, endpointUrl)];\n case \"section\":\n return convertSectionToWidgets(child, endpointUrl);\n case \"fields\":\n return convertFieldsToWidgets(child);\n default:\n return [];\n }\n}\n\nfunction convertTextToWidget(element: TextElement): GoogleChatWidget {\n let text = convertEmoji(element.content);\n\n // Apply style using Google Chat formatting\n if (element.style === \"bold\") {\n text = `*${text}*`;\n } else if (element.style === \"muted\") {\n // GChat doesn't have muted, use regular text\n text = convertEmoji(element.content);\n }\n\n return {\n textParagraph: { text },\n };\n}\n\nfunction convertImageToWidget(element: ImageElement): GoogleChatWidget {\n return {\n image: {\n imageUrl: element.url,\n altText: element.alt || \"Image\",\n },\n };\n}\n\nfunction convertDividerToWidget(_element: DividerElement): GoogleChatWidget {\n return { divider: {} };\n}\n\nfunction convertActionsToWidget(\n element: ActionsElement,\n endpointUrl?: string,\n): GoogleChatWidget {\n const buttons: GoogleChatButton[] = element.children.map((button) =>\n convertButtonToGoogleButton(button, endpointUrl),\n );\n\n return {\n buttonList: { buttons },\n };\n}\n\nfunction convertButtonToGoogleButton(\n button: ButtonElement,\n endpointUrl?: string,\n): GoogleChatButton {\n // For HTTP endpoint apps, the function field must be the endpoint URL,\n // and the action ID is passed via parameters.\n // See: https://developers.google.com/workspace/add-ons/chat/dialogs\n const parameters: Array<{ key: string; value: string }> = [\n { key: \"actionId\", value: button.id },\n ];\n if (button.value) {\n parameters.push({ key: \"value\", value: button.value });\n }\n\n const googleButton: GoogleChatButton = {\n text: convertEmoji(button.label),\n onClick: {\n action: {\n // For HTTP endpoints, function must be the full URL\n // For other deployments (Apps Script, etc.), use just the action ID\n function: endpointUrl || button.id,\n parameters,\n },\n },\n };\n\n // Apply button style colors\n if (button.style === \"primary\") {\n // Blue color for primary\n googleButton.color = { red: 0.2, green: 0.5, blue: 0.9 };\n } else if (button.style === \"danger\") {\n // Red color for danger\n googleButton.color = { red: 0.9, green: 0.2, blue: 0.2 };\n }\n\n return googleButton;\n}\n\nfunction convertSectionToWidgets(\n element: SectionElement,\n endpointUrl?: string,\n): GoogleChatWidget[] {\n const widgets: GoogleChatWidget[] = [];\n for (const child of element.children) {\n widgets.push(...convertChildToWidgets(child, endpointUrl));\n }\n return widgets;\n}\n\nfunction convertFieldsToWidgets(element: FieldsElement): GoogleChatWidget[] {\n // Convert fields to decorated text widgets\n return element.children.map((field) => ({\n decoratedText: {\n topLabel: convertEmoji(field.label),\n text: convertEmoji(field.value),\n },\n }));\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when cards aren't supported.\n */\nexport function cardToFallbackText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(`*${convertEmoji(card.title)}*`);\n }\n\n if (card.subtitle) {\n parts.push(convertEmoji(card.subtitle));\n }\n\n for (const child of card.children) {\n const text = childToFallbackText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction childToFallbackText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return convertEmoji(child.content);\n case \"fields\":\n return child.children\n .map((f) => `*${convertEmoji(f.label)}*: ${convertEmoji(f.value)}`)\n .join(\"\\n\");\n case \"actions\":\n return `[${child.children\n .map((b) => convertEmoji(b.label))\n .join(\"] [\")}]`;\n case \"section\":\n return child.children\n .map((c) => childToFallbackText(c))\n .filter(Boolean)\n .join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Google Chat-specific format conversion using AST-based parsing.\n *\n * Google Chat supports a subset of text formatting:\n * - Bold: *text*\n * - Italic: _text_\n * - Strikethrough: ~text~\n * - Monospace: `text`\n * - Code blocks: ```text```\n * - Links are auto-detected\n *\n * Very similar to Slack's mrkdwn format.\n */\n\nimport {\n BaseFormatConverter,\n type Code,\n type Content,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Link,\n type Paragraph,\n parseMarkdown,\n type Root,\n type Strong,\n type Text,\n} from \"chat\";\n\nexport class GoogleChatFormatConverter extends BaseFormatConverter {\n /**\n * Render an AST to Google Chat format.\n */\n fromAst(ast: Root): string {\n const parts: string[] = [];\n\n for (const node of ast.children) {\n parts.push(this.nodeToGChat(node as Content));\n }\n\n return parts.join(\"\\n\\n\");\n }\n\n /**\n * Parse Google Chat message into an AST.\n */\n toAst(gchatText: string): Root {\n // Convert Google Chat format to standard markdown, then parse\n let markdown = gchatText;\n\n // Bold: *text* -> **text**\n markdown = markdown.replace(/(?<![_*\\\\])\\*([^*\\n]+)\\*(?![_*])/g, \"**$1**\");\n\n // Strikethrough: ~text~ -> ~~text~~\n markdown = markdown.replace(/(?<!~)~([^~\\n]+)~(?!~)/g, \"~~$1~~\");\n\n // Italic and code are the same format as markdown\n\n return parseMarkdown(markdown);\n }\n\n private nodeToGChat(node: Content): string {\n switch (node.type) {\n case \"paragraph\":\n return (node as Paragraph).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n\n case \"text\": {\n // Google Chat: @mentions are passed through as-is\n // To create clickable mentions in Google Chat, you'd need to use <users/{user_id}> format\n // which requires user ID lookup - beyond the scope of format conversion\n return (node as Text).value;\n }\n\n case \"strong\":\n // Markdown **text** -> GChat *text*\n return `*${(node as Strong).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}*`;\n\n case \"emphasis\":\n // Both use _text_\n return `_${(node as Emphasis).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}_`;\n\n case \"delete\":\n // Markdown ~~text~~ -> GChat ~text~\n return `~${(node as Delete).children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\")}~`;\n\n case \"inlineCode\":\n return `\\`${(node as InlineCode).value}\\``;\n\n case \"code\": {\n const codeNode = node as Code;\n return `\\`\\`\\`\\n${codeNode.value}\\n\\`\\`\\``;\n }\n\n case \"link\": {\n // Google Chat auto-detects links, so we just output the URL\n const linkNode = node as Link;\n const linkText = linkNode.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n // If link text matches URL, just output URL\n if (linkText === linkNode.url) {\n return linkNode.url;\n }\n // Otherwise output \"text (url)\"\n return `${linkText} (${linkNode.url})`;\n }\n\n case \"blockquote\":\n // Google Chat doesn't have native blockquote, use > prefix\n return node.children\n .map((child) => `> ${this.nodeToGChat(child as Content)}`)\n .join(\"\\n\");\n\n case \"list\":\n return node.children\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"•\";\n const content = item.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n\n case \"listItem\":\n return node.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n\n case \"break\":\n return \"\\n\";\n\n case \"thematicBreak\":\n return \"---\";\n\n default:\n if (\"children\" in node && Array.isArray(node.children)) {\n return node.children\n .map((child) => this.nodeToGChat(child as Content))\n .join(\"\");\n }\n if (\"value\" in node) {\n return String(node.value);\n }\n return \"\";\n }\n }\n}\n","/**\n * Google Workspace Events API integration for receiving all messages in a space.\n *\n * By default, Google Chat only sends webhooks for @mentions. To receive ALL messages\n * in a space, you need to create a Workspace Events subscription that publishes to\n * a Pub/Sub topic, which then pushes to your webhook endpoint.\n *\n * Setup flow:\n * 1. Create a Pub/Sub topic in your GCP project\n * 2. Create a Pub/Sub push subscription pointing to your /api/webhooks/gchat/pubsub endpoint\n * 3. Call createSpaceSubscription() to subscribe to message events for a space\n * 4. Handle Pub/Sub messages in your webhook with handlePubSubMessage()\n */\n\nimport { google } from \"googleapis\";\nimport type { GoogleChatMessage } from \"./index\";\n\n/** Options for creating a space subscription */\nexport interface CreateSpaceSubscriptionOptions {\n /** The space name (e.g., \"spaces/AAAA...\") */\n spaceName: string;\n /** The Pub/Sub topic to receive events (e.g., \"projects/my-project/topics/my-topic\") */\n pubsubTopic: string;\n /** Optional TTL for the subscription in seconds (default: 1 day, max: 1 day for Chat) */\n ttlSeconds?: number;\n}\n\n/** Result of creating a space subscription */\nexport interface SpaceSubscriptionResult {\n /** The subscription resource name */\n name: string;\n /** When the subscription expires (ISO 8601) */\n expireTime: string;\n}\n\n/** Pub/Sub push message wrapper (what Google sends to your endpoint) */\nexport interface PubSubPushMessage {\n message: {\n /** Base64 encoded event data */\n data: string;\n messageId: string;\n publishTime: string;\n attributes?: Record<string, string>;\n };\n subscription: string;\n}\n\n/** Google Chat reaction data */\nexport interface GoogleChatReaction {\n /** Reaction resource name */\n name: string;\n /** The user who added/removed the reaction */\n user?: {\n name: string;\n displayName?: string;\n type?: string;\n };\n /** The emoji */\n emoji?: {\n unicode?: string;\n };\n}\n\n/** Decoded Workspace Events notification payload */\nexport interface WorkspaceEventNotification {\n /** The subscription that triggered this event */\n subscription: string;\n /** The resource being watched (e.g., \"//chat.googleapis.com/spaces/AAAA\") */\n targetResource: string;\n /** Event type (e.g., \"google.workspace.chat.message.v1.created\") */\n eventType: string;\n /** When the event occurred */\n eventTime: string;\n /** Space info */\n space?: {\n name: string;\n type: string;\n };\n /** Present for message.created events */\n message?: GoogleChatMessage;\n /** Present for reaction.created/deleted events */\n reaction?: GoogleChatReaction;\n}\n\n/** Service account credentials for authentication */\nexport interface ServiceAccountCredentials {\n client_email: string;\n private_key: string;\n project_id?: string;\n}\n\n/** Auth options - service account, ADC, or custom auth client */\nexport type WorkspaceEventsAuthOptions =\n | { credentials: ServiceAccountCredentials; impersonateUser?: string }\n | { useApplicationDefaultCredentials: true; impersonateUser?: string }\n | { auth: Parameters<typeof google.workspaceevents>[0][\"auth\"] };\n\n/**\n * Create a Workspace Events subscription to receive all messages in a Chat space.\n *\n * Prerequisites:\n * - Enable the \"Google Workspace Events API\" in your GCP project\n * - Create a Pub/Sub topic and grant the Chat service account publish permissions\n * - The calling user/service account needs permission to access the space\n *\n * @example\n * ```typescript\n * const result = await createSpaceSubscription({\n * spaceName: \"spaces/AAAAxxxxxx\",\n * pubsubTopic: \"projects/my-project/topics/chat-events\",\n * }, {\n * credentials: {\n * client_email: \"...\",\n * private_key: \"...\",\n * }\n * });\n * ```\n */\nexport async function createSpaceSubscription(\n options: CreateSpaceSubscriptionOptions,\n auth: WorkspaceEventsAuthOptions,\n): Promise<SpaceSubscriptionResult> {\n const { spaceName, pubsubTopic, ttlSeconds = 86400 } = options; // Default 1 day\n\n // Set up auth\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n // For domain-wide delegation, impersonate a user\n subject: auth.impersonateUser,\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces.readonly\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n // Note: ADC with impersonation requires different setup\n scopes: [\n \"https://www.googleapis.com/auth/chat.spaces.readonly\",\n \"https://www.googleapis.com/auth/chat.messages.readonly\",\n ],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n // Create the subscription\n const response = await workspaceEvents.subscriptions.create({\n requestBody: {\n targetResource: `//chat.googleapis.com/${spaceName}`,\n eventTypes: [\n \"google.workspace.chat.message.v1.created\",\n \"google.workspace.chat.message.v1.updated\",\n \"google.workspace.chat.reaction.v1.created\",\n \"google.workspace.chat.reaction.v1.deleted\",\n ],\n notificationEndpoint: {\n pubsubTopic,\n },\n payloadOptions: {\n includeResource: true,\n },\n ttl: `${ttlSeconds}s`,\n },\n });\n\n // The create operation returns a long-running operation\n // For simplicity, we'll return the operation name - in production you might want to poll for completion\n const operation = response.data;\n\n if (operation.done && operation.response) {\n const subscription = operation.response as {\n name?: string;\n expireTime?: string;\n };\n return {\n name: subscription.name || \"\",\n expireTime: subscription.expireTime || \"\",\n };\n }\n\n // Operation is still pending - return operation name\n // The subscription will be created asynchronously\n return {\n name: operation.name || \"pending\",\n expireTime: \"\",\n };\n}\n\n/**\n * List active subscriptions for a target resource.\n */\nexport async function listSpaceSubscriptions(\n spaceName: string,\n auth: WorkspaceEventsAuthOptions,\n): Promise<Array<{ name: string; expireTime: string; eventTypes: string[] }>> {\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n subject: auth.impersonateUser,\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n const response = await workspaceEvents.subscriptions.list({\n filter: `target_resource=\"//chat.googleapis.com/${spaceName}\"`,\n });\n\n return (response.data.subscriptions || []).map((sub) => ({\n name: sub.name || \"\",\n expireTime: sub.expireTime || \"\",\n eventTypes: sub.eventTypes || [],\n }));\n}\n\n/**\n * Delete a Workspace Events subscription.\n */\nexport async function deleteSpaceSubscription(\n subscriptionName: string,\n auth: WorkspaceEventsAuthOptions,\n): Promise<void> {\n let authClient: Parameters<typeof google.workspaceevents>[0][\"auth\"];\n\n if (\"credentials\" in auth) {\n authClient = new google.auth.JWT({\n email: auth.credentials.client_email,\n key: auth.credentials.private_key,\n subject: auth.impersonateUser,\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else if (\"useApplicationDefaultCredentials\" in auth) {\n authClient = new google.auth.GoogleAuth({\n scopes: [\"https://www.googleapis.com/auth/chat.spaces.readonly\"],\n });\n } else {\n // Custom auth client (e.g., Vercel OIDC)\n authClient = auth.auth;\n }\n\n const workspaceEvents = google.workspaceevents({\n version: \"v1\",\n auth: authClient,\n });\n\n await workspaceEvents.subscriptions.delete({\n name: subscriptionName,\n });\n}\n\n/**\n * Decode a Pub/Sub push message into a Workspace Event notification.\n *\n * The message uses CloudEvents format where event metadata is in attributes\n * (ce-type, ce-source, ce-subject, ce-time) and the payload is base64 encoded.\n *\n * @example\n * ```typescript\n * // In your /api/webhooks/gchat/pubsub route:\n * const body = await request.json();\n * const event = decodePubSubMessage(body);\n *\n * if (event.eventType === \"google.workspace.chat.message.v1.created\") {\n * // Handle new message\n * console.log(\"New message:\", event.message?.text);\n * }\n * ```\n */\nexport function decodePubSubMessage(\n pushMessage: PubSubPushMessage,\n): WorkspaceEventNotification {\n // Decode the base64 payload\n const data = Buffer.from(pushMessage.message.data, \"base64\").toString(\n \"utf-8\",\n );\n const payload = JSON.parse(data) as {\n message?: GoogleChatMessage;\n reaction?: GoogleChatReaction;\n };\n\n // Extract CloudEvents metadata from attributes\n const attributes = pushMessage.message.attributes || {};\n\n return {\n subscription: pushMessage.subscription,\n targetResource: attributes[\"ce-subject\"] || \"\",\n eventType: attributes[\"ce-type\"] || \"\",\n eventTime: attributes[\"ce-time\"] || pushMessage.message.publishTime,\n message: payload.message,\n reaction: payload.reaction,\n };\n}\n\n/**\n * Verify a Pub/Sub push message is authentic.\n * In production, you should verify the JWT token in the Authorization header.\n *\n * @see https://cloud.google.com/pubsub/docs/authenticate-push-subscriptions\n */\nexport function verifyPubSubRequest(\n request: Request,\n _expectedAudience?: string,\n): boolean {\n // Basic check - Pub/Sub always sends POST with specific content type\n if (request.method !== \"POST\") {\n return false;\n }\n\n const contentType = request.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n return false;\n }\n\n // For full verification, you would:\n // 1. Extract the Bearer token from Authorization header\n // 2. Verify it's a valid Google-signed JWT\n // 3. Check the audience matches your endpoint\n // This requires additional setup - see Google's docs\n\n return true;\n}\n"],"mappings":"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","names":["convertEmojiPlaceholders","google","google","response","convertEmojiPlaceholders"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-adapter/gchat",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "Google Chat adapter for chat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "googleapis": "^144.0.0",
20
- "chat": "4.0.2"
20
+ "chat": "4.1.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.10.2",