@chat-adapter/gchat 4.0.1 → 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 +28 -7
- package/dist/index.js +70 -37
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CardElement, BaseFormatConverter, Root, Adapter, ChatInstance, WebhookOptions,
|
|
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
|
/**
|
|
@@ -64,10 +64,22 @@ interface GoogleChatButton {
|
|
|
64
64
|
blue: number;
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Options for card conversion.
|
|
69
|
+
*/
|
|
70
|
+
interface CardConversionOptions {
|
|
71
|
+
/** Unique card ID for interactive cards */
|
|
72
|
+
cardId?: string;
|
|
73
|
+
/**
|
|
74
|
+
* HTTP endpoint URL for button actions.
|
|
75
|
+
* Required for HTTP endpoint apps - button clicks will be routed to this URL.
|
|
76
|
+
*/
|
|
77
|
+
endpointUrl?: string;
|
|
78
|
+
}
|
|
67
79
|
/**
|
|
68
80
|
* Convert a CardElement to Google Chat Card v2 format.
|
|
69
81
|
*/
|
|
70
|
-
declare function cardToGoogleCard(card: CardElement,
|
|
82
|
+
declare function cardToGoogleCard(card: CardElement, options?: CardConversionOptions | string): GoogleChatCard;
|
|
71
83
|
/**
|
|
72
84
|
* Generate fallback text from a card element.
|
|
73
85
|
* Used when cards aren't supported.
|
|
@@ -275,6 +287,12 @@ interface GoogleChatAdapterBaseConfig {
|
|
|
275
287
|
* This user must have access to the Chat spaces you want to subscribe to.
|
|
276
288
|
*/
|
|
277
289
|
impersonateUser?: string;
|
|
290
|
+
/**
|
|
291
|
+
* HTTP endpoint URL for button click actions.
|
|
292
|
+
* Required for HTTP endpoint apps - button clicks will be routed to this URL.
|
|
293
|
+
* Should be the full URL of your webhook endpoint (e.g., "https://your-app.vercel.app/api/webhooks/gchat")
|
|
294
|
+
*/
|
|
295
|
+
endpointUrl?: string;
|
|
278
296
|
}
|
|
279
297
|
/** Config using service account credentials (JSON key file) */
|
|
280
298
|
interface GoogleChatAdapterServiceAccountConfig extends GoogleChatAdapterBaseConfig {
|
|
@@ -431,6 +449,8 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
431
449
|
private pendingSubscriptions;
|
|
432
450
|
/** Chat API client with impersonation for user-context operations (DMs, etc.) */
|
|
433
451
|
private impersonatedChatApi?;
|
|
452
|
+
/** HTTP endpoint URL for button click actions */
|
|
453
|
+
private endpointUrl?;
|
|
434
454
|
constructor(config: GoogleChatAdapterConfig);
|
|
435
455
|
initialize(chat: ChatInstance): Promise<void>;
|
|
436
456
|
/**
|
|
@@ -481,7 +501,8 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
481
501
|
private handleAddedToSpace;
|
|
482
502
|
/**
|
|
483
503
|
* Handle card button clicks.
|
|
484
|
-
*
|
|
504
|
+
* For HTTP endpoint apps, the actionId is passed via parameters (since function is the URL).
|
|
505
|
+
* For other deployments, actionId may be in invokedFunction.
|
|
485
506
|
*/
|
|
486
507
|
private handleCardClick;
|
|
487
508
|
/**
|
|
@@ -489,20 +510,20 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
489
510
|
*/
|
|
490
511
|
private handleMessageEvent;
|
|
491
512
|
private parseGoogleChatMessage;
|
|
492
|
-
postMessage(threadId: string, message:
|
|
513
|
+
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
493
514
|
/**
|
|
494
|
-
* Extract card element from a
|
|
515
|
+
* Extract card element from a message if present.
|
|
495
516
|
*/
|
|
496
517
|
private extractCard;
|
|
497
518
|
/**
|
|
498
|
-
* Extract files from a
|
|
519
|
+
* Extract files from a message if present.
|
|
499
520
|
*/
|
|
500
521
|
private extractFiles;
|
|
501
522
|
/**
|
|
502
523
|
* Create an Attachment object from a Google Chat attachment.
|
|
503
524
|
*/
|
|
504
525
|
private createAttachment;
|
|
505
|
-
editMessage(threadId: string, messageId: string, message:
|
|
526
|
+
editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
506
527
|
deleteMessage(_threadId: string, messageId: string): Promise<void>;
|
|
507
528
|
addReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
508
529
|
removeReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
function convertEmoji(text) {
|
|
15
15
|
return convertEmojiPlaceholders(text, "gchat");
|
|
16
16
|
}
|
|
17
|
-
function cardToGoogleCard(card,
|
|
17
|
+
function cardToGoogleCard(card, options) {
|
|
18
|
+
const opts = typeof options === "string" ? { cardId: options } : options || {};
|
|
18
19
|
const sections = [];
|
|
19
20
|
let header;
|
|
20
21
|
if (card.title || card.subtitle || card.imageUrl) {
|
|
@@ -36,10 +37,10 @@ function cardToGoogleCard(card, cardId) {
|
|
|
36
37
|
sections.push({ widgets: currentWidgets });
|
|
37
38
|
currentWidgets = [];
|
|
38
39
|
}
|
|
39
|
-
const sectionWidgets = convertSectionToWidgets(child);
|
|
40
|
+
const sectionWidgets = convertSectionToWidgets(child, opts.endpointUrl);
|
|
40
41
|
sections.push({ widgets: sectionWidgets });
|
|
41
42
|
} else {
|
|
42
|
-
const widgets = convertChildToWidgets(child);
|
|
43
|
+
const widgets = convertChildToWidgets(child, opts.endpointUrl);
|
|
43
44
|
currentWidgets.push(...widgets);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -59,12 +60,12 @@ function cardToGoogleCard(card, cardId) {
|
|
|
59
60
|
if (header) {
|
|
60
61
|
googleCard.card.header = header;
|
|
61
62
|
}
|
|
62
|
-
if (cardId) {
|
|
63
|
-
googleCard.cardId = cardId;
|
|
63
|
+
if (opts.cardId) {
|
|
64
|
+
googleCard.cardId = opts.cardId;
|
|
64
65
|
}
|
|
65
66
|
return googleCard;
|
|
66
67
|
}
|
|
67
|
-
function convertChildToWidgets(child) {
|
|
68
|
+
function convertChildToWidgets(child, endpointUrl) {
|
|
68
69
|
switch (child.type) {
|
|
69
70
|
case "text":
|
|
70
71
|
return [convertTextToWidget(child)];
|
|
@@ -73,9 +74,9 @@ function convertChildToWidgets(child) {
|
|
|
73
74
|
case "divider":
|
|
74
75
|
return [convertDividerToWidget(child)];
|
|
75
76
|
case "actions":
|
|
76
|
-
return [convertActionsToWidget(child)];
|
|
77
|
+
return [convertActionsToWidget(child, endpointUrl)];
|
|
77
78
|
case "section":
|
|
78
|
-
return convertSectionToWidgets(child);
|
|
79
|
+
return convertSectionToWidgets(child, endpointUrl);
|
|
79
80
|
case "fields":
|
|
80
81
|
return convertFieldsToWidgets(child);
|
|
81
82
|
default:
|
|
@@ -104,21 +105,29 @@ function convertImageToWidget(element) {
|
|
|
104
105
|
function convertDividerToWidget(_element) {
|
|
105
106
|
return { divider: {} };
|
|
106
107
|
}
|
|
107
|
-
function convertActionsToWidget(element) {
|
|
108
|
+
function convertActionsToWidget(element, endpointUrl) {
|
|
108
109
|
const buttons = element.children.map(
|
|
109
|
-
(button) => convertButtonToGoogleButton(button)
|
|
110
|
+
(button) => convertButtonToGoogleButton(button, endpointUrl)
|
|
110
111
|
);
|
|
111
112
|
return {
|
|
112
113
|
buttonList: { buttons }
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
|
-
function convertButtonToGoogleButton(button) {
|
|
116
|
+
function convertButtonToGoogleButton(button, endpointUrl) {
|
|
117
|
+
const parameters = [
|
|
118
|
+
{ key: "actionId", value: button.id }
|
|
119
|
+
];
|
|
120
|
+
if (button.value) {
|
|
121
|
+
parameters.push({ key: "value", value: button.value });
|
|
122
|
+
}
|
|
116
123
|
const googleButton = {
|
|
117
124
|
text: convertEmoji(button.label),
|
|
118
125
|
onClick: {
|
|
119
126
|
action: {
|
|
120
|
-
function
|
|
121
|
-
|
|
127
|
+
// For HTTP endpoints, function must be the full URL
|
|
128
|
+
// For other deployments (Apps Script, etc.), use just the action ID
|
|
129
|
+
function: endpointUrl || button.id,
|
|
130
|
+
parameters
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
};
|
|
@@ -129,10 +138,10 @@ function convertButtonToGoogleButton(button) {
|
|
|
129
138
|
}
|
|
130
139
|
return googleButton;
|
|
131
140
|
}
|
|
132
|
-
function convertSectionToWidgets(element) {
|
|
141
|
+
function convertSectionToWidgets(element, endpointUrl) {
|
|
133
142
|
const widgets = [];
|
|
134
143
|
for (const child of element.children) {
|
|
135
|
-
widgets.push(...convertChildToWidgets(child));
|
|
144
|
+
widgets.push(...convertChildToWidgets(child, endpointUrl));
|
|
136
145
|
}
|
|
137
146
|
return widgets;
|
|
138
147
|
}
|
|
@@ -425,13 +434,17 @@ var GoogleChatAdapter = class {
|
|
|
425
434
|
pendingSubscriptions = /* @__PURE__ */ new Map();
|
|
426
435
|
/** Chat API client with impersonation for user-context operations (DMs, etc.) */
|
|
427
436
|
impersonatedChatApi;
|
|
437
|
+
/** HTTP endpoint URL for button click actions */
|
|
438
|
+
endpointUrl;
|
|
428
439
|
constructor(config) {
|
|
429
440
|
this.userName = config.userName || "bot";
|
|
430
441
|
this.pubsubTopic = config.pubsubTopic;
|
|
431
442
|
this.impersonateUser = config.impersonateUser;
|
|
443
|
+
this.endpointUrl = config.endpointUrl;
|
|
432
444
|
let auth;
|
|
433
445
|
const scopes = [
|
|
434
446
|
"https://www.googleapis.com/auth/chat.bot",
|
|
447
|
+
"https://www.googleapis.com/auth/chat.messages.readonly",
|
|
435
448
|
"https://www.googleapis.com/auth/chat.messages.reactions.create",
|
|
436
449
|
"https://www.googleapis.com/auth/chat.messages.reactions",
|
|
437
450
|
"https://www.googleapis.com/auth/chat.spaces.create"
|
|
@@ -465,7 +478,8 @@ var GoogleChatAdapter = class {
|
|
|
465
478
|
key: this.credentials.private_key,
|
|
466
479
|
scopes: [
|
|
467
480
|
"https://www.googleapis.com/auth/chat.spaces",
|
|
468
|
-
"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"
|
|
469
483
|
],
|
|
470
484
|
subject: this.impersonateUser
|
|
471
485
|
});
|
|
@@ -477,7 +491,8 @@ var GoogleChatAdapter = class {
|
|
|
477
491
|
const impersonatedAuth = new google2.auth.GoogleAuth({
|
|
478
492
|
scopes: [
|
|
479
493
|
"https://www.googleapis.com/auth/chat.spaces",
|
|
480
|
-
"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"
|
|
481
496
|
],
|
|
482
497
|
clientOptions: {
|
|
483
498
|
subject: this.impersonateUser
|
|
@@ -690,6 +705,16 @@ var GoogleChatAdapter = class {
|
|
|
690
705
|
return null;
|
|
691
706
|
}
|
|
692
707
|
async handleWebhook(request, options) {
|
|
708
|
+
if (!this.endpointUrl) {
|
|
709
|
+
try {
|
|
710
|
+
const url = new URL(request.url);
|
|
711
|
+
this.endpointUrl = url.toString();
|
|
712
|
+
this.logger?.debug("Auto-detected endpoint URL", {
|
|
713
|
+
endpointUrl: this.endpointUrl
|
|
714
|
+
});
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
693
718
|
const body = await request.text();
|
|
694
719
|
this.logger?.debug("GChat webhook raw body", { body });
|
|
695
720
|
let parsed;
|
|
@@ -721,16 +746,9 @@ var GoogleChatAdapter = class {
|
|
|
721
746
|
const invokedFunction = event.commonEventObject?.invokedFunction;
|
|
722
747
|
if (buttonClickedPayload || invokedFunction) {
|
|
723
748
|
this.handleCardClick(event, options);
|
|
724
|
-
return new Response(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
type: "UPDATE_MESSAGE"
|
|
728
|
-
}
|
|
729
|
-
}),
|
|
730
|
-
{
|
|
731
|
-
headers: { "Content-Type": "application/json" }
|
|
732
|
-
}
|
|
733
|
-
);
|
|
749
|
+
return new Response(JSON.stringify({}), {
|
|
750
|
+
headers: { "Content-Type": "application/json" }
|
|
751
|
+
});
|
|
734
752
|
}
|
|
735
753
|
const messagePayload = event.chat?.messagePayload;
|
|
736
754
|
if (messagePayload) {
|
|
@@ -960,7 +978,8 @@ var GoogleChatAdapter = class {
|
|
|
960
978
|
}
|
|
961
979
|
/**
|
|
962
980
|
* Handle card button clicks.
|
|
963
|
-
*
|
|
981
|
+
* For HTTP endpoint apps, the actionId is passed via parameters (since function is the URL).
|
|
982
|
+
* For other deployments, actionId may be in invokedFunction.
|
|
964
983
|
*/
|
|
965
984
|
handleCardClick(event, options) {
|
|
966
985
|
if (!this.chat) {
|
|
@@ -969,9 +988,12 @@ var GoogleChatAdapter = class {
|
|
|
969
988
|
}
|
|
970
989
|
const buttonPayload = event.chat?.buttonClickedPayload;
|
|
971
990
|
const commonEvent = event.commonEventObject;
|
|
972
|
-
const actionId = commonEvent?.invokedFunction;
|
|
991
|
+
const actionId = commonEvent?.parameters?.actionId || commonEvent?.invokedFunction;
|
|
973
992
|
if (!actionId) {
|
|
974
|
-
this.logger?.debug("Card click missing
|
|
993
|
+
this.logger?.debug("Card click missing actionId", {
|
|
994
|
+
parameters: commonEvent?.parameters,
|
|
995
|
+
invokedFunction: commonEvent?.invokedFunction
|
|
996
|
+
});
|
|
975
997
|
return;
|
|
976
998
|
}
|
|
977
999
|
const value = commonEvent?.parameters?.value;
|
|
@@ -1088,7 +1110,11 @@ var GoogleChatAdapter = class {
|
|
|
1088
1110
|
}
|
|
1089
1111
|
const card = this.extractCard(message);
|
|
1090
1112
|
if (card) {
|
|
1091
|
-
const
|
|
1113
|
+
const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1114
|
+
const googleCard = cardToGoogleCard(card, {
|
|
1115
|
+
cardId,
|
|
1116
|
+
endpointUrl: this.endpointUrl
|
|
1117
|
+
});
|
|
1092
1118
|
this.logger?.debug("GChat API: spaces.messages.create (card)", {
|
|
1093
1119
|
spaceName,
|
|
1094
1120
|
threadName,
|
|
@@ -1143,7 +1169,7 @@ var GoogleChatAdapter = class {
|
|
|
1143
1169
|
}
|
|
1144
1170
|
}
|
|
1145
1171
|
/**
|
|
1146
|
-
* Extract card element from a
|
|
1172
|
+
* Extract card element from a message if present.
|
|
1147
1173
|
*/
|
|
1148
1174
|
extractCard(message) {
|
|
1149
1175
|
if (isCardElement(message)) {
|
|
@@ -1155,7 +1181,7 @@ var GoogleChatAdapter = class {
|
|
|
1155
1181
|
return null;
|
|
1156
1182
|
}
|
|
1157
1183
|
/**
|
|
1158
|
-
* Extract files from a
|
|
1184
|
+
* Extract files from a message if present.
|
|
1159
1185
|
*/
|
|
1160
1186
|
extractFiles(message) {
|
|
1161
1187
|
if (typeof message === "object" && message !== null && "files" in message) {
|
|
@@ -1211,9 +1237,14 @@ var GoogleChatAdapter = class {
|
|
|
1211
1237
|
try {
|
|
1212
1238
|
const card = this.extractCard(message);
|
|
1213
1239
|
if (card) {
|
|
1214
|
-
const
|
|
1240
|
+
const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1241
|
+
const googleCard = cardToGoogleCard(card, {
|
|
1242
|
+
cardId,
|
|
1243
|
+
endpointUrl: this.endpointUrl
|
|
1244
|
+
});
|
|
1215
1245
|
this.logger?.debug("GChat API: spaces.messages.update (card)", {
|
|
1216
|
-
messageId
|
|
1246
|
+
messageId,
|
|
1247
|
+
cardId
|
|
1217
1248
|
});
|
|
1218
1249
|
const response2 = await this.chatApi.spaces.messages.update({
|
|
1219
1250
|
name: messageId,
|
|
@@ -1404,12 +1435,14 @@ var GoogleChatAdapter = class {
|
|
|
1404
1435
|
}
|
|
1405
1436
|
async fetchMessages(threadId, options = {}) {
|
|
1406
1437
|
const { spaceName } = this.decodeThreadId(threadId);
|
|
1438
|
+
const api = this.impersonatedChatApi || this.chatApi;
|
|
1407
1439
|
try {
|
|
1408
1440
|
this.logger?.debug("GChat API: spaces.messages.list", {
|
|
1409
1441
|
spaceName,
|
|
1410
|
-
pageSize: options.limit || 100
|
|
1442
|
+
pageSize: options.limit || 100,
|
|
1443
|
+
impersonated: !!this.impersonatedChatApi
|
|
1411
1444
|
});
|
|
1412
|
-
const response = await
|
|
1445
|
+
const response = await api.spaces.messages.list({
|
|
1413
1446
|
parent: spaceName,
|
|
1414
1447
|
pageSize: options.limit || 100,
|
|
1415
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\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\n constructor(config: GoogleChatAdapterConfig) {\n this.userName = config.userName || \"bot\";\n this.pubsubTopic = config.pubsubTopic;\n this.impersonateUser = config.impersonateUser;\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 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 // Google Chat expects an actionResponse for card clicks\n // Return UPDATE_MESSAGE with no card to acknowledge without changing the card\n return new Response(\n JSON.stringify({\n actionResponse: {\n type: \"UPDATE_MESSAGE\",\n },\n }),\n {\n headers: { \"Content-Type\": \"application/json\" },\n },\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 * Google Chat sends action data via commonEventObject.invokedFunction and parameters.\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 from invokedFunction (this is the button's id)\n const actionId = commonEvent?.invokedFunction;\n if (!actionId) {\n this.logger?.debug(\"Card click missing invokedFunction\");\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 const googleCard = cardToGoogleCard(card);\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 const googleCard = cardToGoogleCard(card);\n\n this.logger?.debug(\"GChat API: spaces.messages.update (card)\", {\n messageId,\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 * Convert a CardElement to Google Chat Card v2 format.\n */\nexport function cardToGoogleCard(\n card: CardElement,\n cardId?: string,\n): GoogleChatCard {\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);\n sections.push({ widgets: sectionWidgets });\n } else {\n // Add to current widgets\n const widgets = convertChildToWidgets(child);\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 (cardId) {\n googleCard.cardId = cardId;\n }\n\n return googleCard;\n}\n\n/**\n * Convert a card child element to Google Chat widgets.\n */\nfunction convertChildToWidgets(child: CardChild): 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)];\n case \"section\":\n return convertSectionToWidgets(child);\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(element: ActionsElement): GoogleChatWidget {\n const buttons: GoogleChatButton[] = element.children.map((button) =>\n convertButtonToGoogleButton(button),\n );\n\n return {\n buttonList: { buttons },\n };\n}\n\nfunction convertButtonToGoogleButton(button: ButtonElement): GoogleChatButton {\n const googleButton: GoogleChatButton = {\n text: convertEmoji(button.label),\n onClick: {\n action: {\n function: button.id,\n parameters: button.value ? [{ key: \"value\", value: button.value }] : [],\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(element: SectionElement): GoogleChatWidget[] {\n const widgets: GoogleChatWidget[] = [];\n for (const child of element.children) {\n widgets.push(...convertChildToWidgets(child));\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
|
|
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
|
|
20
|
+
"chat": "4.1.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.10.2",
|