@chat-adapter/gchat 4.1.0 → 4.2.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 -3
- package/dist/index.js +187 -48
- 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, AdapterPostableMessage, RawMessage, EmojiValue, FetchOptions,
|
|
1
|
+
import { CardElement, BaseFormatConverter, Root, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage, RawMessage, EmojiValue, FetchOptions, FetchResult, ThreadInfo, Message, FormattedContent } from 'chat';
|
|
2
2
|
import { google } from 'googleapis';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -451,6 +451,8 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
451
451
|
private impersonatedChatApi?;
|
|
452
452
|
/** HTTP endpoint URL for button click actions */
|
|
453
453
|
private endpointUrl?;
|
|
454
|
+
/** In-memory user info cache for fast lookups */
|
|
455
|
+
private userInfoCache;
|
|
454
456
|
constructor(config: GoogleChatAdapterConfig);
|
|
455
457
|
initialize(chat: ChatInstance): Promise<void>;
|
|
456
458
|
/**
|
|
@@ -538,7 +540,30 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
538
540
|
* @param userId - The user's resource name (e.g., "users/123456")
|
|
539
541
|
*/
|
|
540
542
|
openDM(userId: string): Promise<string>;
|
|
541
|
-
fetchMessages(threadId: string, options?: FetchOptions): Promise<
|
|
543
|
+
fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<unknown>>;
|
|
544
|
+
/**
|
|
545
|
+
* Fetch messages in backward direction (most recent first).
|
|
546
|
+
* GChat API defaults to createTime ASC (oldest first), so we request DESC
|
|
547
|
+
* to get the most recent messages, then reverse for chronological order within page.
|
|
548
|
+
*/
|
|
549
|
+
private fetchMessagesBackward;
|
|
550
|
+
/**
|
|
551
|
+
* Fetch messages in forward direction (oldest first).
|
|
552
|
+
*
|
|
553
|
+
* GChat API defaults to createTime ASC (oldest first), which is what we want.
|
|
554
|
+
* For forward pagination, we:
|
|
555
|
+
* 1. If no cursor: Fetch ALL messages (already in chronological order)
|
|
556
|
+
* 2. If cursor: Cursor is a message name, skip to after that message
|
|
557
|
+
*
|
|
558
|
+
* Note: This is less efficient than backward for large message histories,
|
|
559
|
+
* as it requires fetching all messages to find the cursor position.
|
|
560
|
+
*/
|
|
561
|
+
private fetchMessagesForward;
|
|
562
|
+
/**
|
|
563
|
+
* Parse a message from the list API into the standard Message format.
|
|
564
|
+
* Resolves user display names and properly determines isMe.
|
|
565
|
+
*/
|
|
566
|
+
private parseGChatListMessage;
|
|
542
567
|
fetchThread(threadId: string): Promise<ThreadInfo>;
|
|
543
568
|
encodeThreadId(platformData: GoogleChatThreadId): string;
|
|
544
569
|
/**
|
|
@@ -574,7 +599,7 @@ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown>
|
|
|
574
599
|
*/
|
|
575
600
|
private cacheUserInfo;
|
|
576
601
|
/**
|
|
577
|
-
* Get cached user info.
|
|
602
|
+
* Get cached user info. Checks in-memory cache first, then falls back to state adapter.
|
|
578
603
|
*/
|
|
579
604
|
private getCachedUserInfo;
|
|
580
605
|
/**
|
package/dist/index.js
CHANGED
|
@@ -436,6 +436,8 @@ var GoogleChatAdapter = class {
|
|
|
436
436
|
impersonatedChatApi;
|
|
437
437
|
/** HTTP endpoint URL for button click actions */
|
|
438
438
|
endpointUrl;
|
|
439
|
+
/** In-memory user info cache for fast lookups */
|
|
440
|
+
userInfoCache = /* @__PURE__ */ new Map();
|
|
439
441
|
constructor(config) {
|
|
440
442
|
this.userName = config.userName || "bot";
|
|
441
443
|
this.pubsubTopic = config.pubsubTopic;
|
|
@@ -1434,52 +1436,174 @@ var GoogleChatAdapter = class {
|
|
|
1434
1436
|
}
|
|
1435
1437
|
}
|
|
1436
1438
|
async fetchMessages(threadId, options = {}) {
|
|
1437
|
-
const { spaceName } = this.decodeThreadId(threadId);
|
|
1439
|
+
const { spaceName, threadName } = this.decodeThreadId(threadId);
|
|
1440
|
+
const direction = options.direction ?? "backward";
|
|
1441
|
+
const limit = options.limit || 100;
|
|
1438
1442
|
const api = this.impersonatedChatApi || this.chatApi;
|
|
1439
1443
|
try {
|
|
1440
|
-
|
|
1444
|
+
const filter = threadName ? `thread.name = "${threadName}"` : void 0;
|
|
1445
|
+
if (direction === "forward") {
|
|
1446
|
+
return this.fetchMessagesForward(
|
|
1447
|
+
api,
|
|
1448
|
+
spaceName,
|
|
1449
|
+
threadId,
|
|
1450
|
+
filter,
|
|
1451
|
+
limit,
|
|
1452
|
+
options.cursor
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
return this.fetchMessagesBackward(
|
|
1456
|
+
api,
|
|
1441
1457
|
spaceName,
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1458
|
+
threadId,
|
|
1459
|
+
filter,
|
|
1460
|
+
limit,
|
|
1461
|
+
options.cursor
|
|
1462
|
+
);
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
this.handleGoogleChatError(error, "fetchMessages");
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Fetch messages in backward direction (most recent first).
|
|
1469
|
+
* GChat API defaults to createTime ASC (oldest first), so we request DESC
|
|
1470
|
+
* to get the most recent messages, then reverse for chronological order within page.
|
|
1471
|
+
*/
|
|
1472
|
+
async fetchMessagesBackward(api, spaceName, threadId, filter, limit, cursor) {
|
|
1473
|
+
this.logger?.debug("GChat API: spaces.messages.list (backward)", {
|
|
1474
|
+
spaceName,
|
|
1475
|
+
filter,
|
|
1476
|
+
pageSize: limit,
|
|
1477
|
+
cursor
|
|
1478
|
+
});
|
|
1479
|
+
const response = await api.spaces.messages.list({
|
|
1480
|
+
parent: spaceName,
|
|
1481
|
+
pageSize: limit,
|
|
1482
|
+
pageToken: cursor,
|
|
1483
|
+
filter,
|
|
1484
|
+
orderBy: "createTime desc"
|
|
1485
|
+
// Get newest messages first
|
|
1486
|
+
});
|
|
1487
|
+
const rawMessages = (response.data.messages || []).reverse();
|
|
1488
|
+
this.logger?.debug("GChat API: spaces.messages.list response (backward)", {
|
|
1489
|
+
messageCount: rawMessages.length,
|
|
1490
|
+
hasNextPageToken: !!response.data.nextPageToken
|
|
1491
|
+
});
|
|
1492
|
+
const messages = await Promise.all(
|
|
1493
|
+
rawMessages.map(
|
|
1494
|
+
(msg) => this.parseGChatListMessage(msg, spaceName, threadId)
|
|
1495
|
+
)
|
|
1496
|
+
);
|
|
1497
|
+
return {
|
|
1498
|
+
messages,
|
|
1499
|
+
// nextPageToken points to older messages (backward pagination)
|
|
1500
|
+
nextCursor: response.data.nextPageToken ?? void 0
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Fetch messages in forward direction (oldest first).
|
|
1505
|
+
*
|
|
1506
|
+
* GChat API defaults to createTime ASC (oldest first), which is what we want.
|
|
1507
|
+
* For forward pagination, we:
|
|
1508
|
+
* 1. If no cursor: Fetch ALL messages (already in chronological order)
|
|
1509
|
+
* 2. If cursor: Cursor is a message name, skip to after that message
|
|
1510
|
+
*
|
|
1511
|
+
* Note: This is less efficient than backward for large message histories,
|
|
1512
|
+
* as it requires fetching all messages to find the cursor position.
|
|
1513
|
+
*/
|
|
1514
|
+
async fetchMessagesForward(api, spaceName, threadId, filter, limit, cursor) {
|
|
1515
|
+
this.logger?.debug("GChat API: spaces.messages.list (forward)", {
|
|
1516
|
+
spaceName,
|
|
1517
|
+
filter,
|
|
1518
|
+
limit,
|
|
1519
|
+
cursor
|
|
1520
|
+
});
|
|
1521
|
+
const allRawMessages = [];
|
|
1522
|
+
let pageToken;
|
|
1523
|
+
do {
|
|
1445
1524
|
const response = await api.spaces.messages.list({
|
|
1446
1525
|
parent: spaceName,
|
|
1447
|
-
pageSize:
|
|
1448
|
-
|
|
1526
|
+
pageSize: 1e3,
|
|
1527
|
+
// Max page size for efficiency
|
|
1528
|
+
pageToken,
|
|
1529
|
+
filter
|
|
1530
|
+
// Default orderBy is createTime ASC (oldest first) - what we want
|
|
1449
1531
|
});
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1532
|
+
const pageMessages = response.data.messages || [];
|
|
1533
|
+
allRawMessages.push(...pageMessages);
|
|
1534
|
+
pageToken = response.data.nextPageToken ?? void 0;
|
|
1535
|
+
} while (pageToken);
|
|
1536
|
+
this.logger?.debug(
|
|
1537
|
+
"GChat API: fetched all messages for forward pagination",
|
|
1538
|
+
{
|
|
1539
|
+
totalCount: allRawMessages.length
|
|
1540
|
+
}
|
|
1541
|
+
);
|
|
1542
|
+
let startIndex = 0;
|
|
1543
|
+
if (cursor) {
|
|
1544
|
+
const cursorIndex = allRawMessages.findIndex(
|
|
1545
|
+
(msg) => msg.name === cursor
|
|
1546
|
+
);
|
|
1547
|
+
if (cursorIndex >= 0) {
|
|
1548
|
+
startIndex = cursorIndex + 1;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const selectedMessages = allRawMessages.slice(
|
|
1552
|
+
startIndex,
|
|
1553
|
+
startIndex + limit
|
|
1554
|
+
);
|
|
1555
|
+
const messages = await Promise.all(
|
|
1556
|
+
selectedMessages.map(
|
|
1557
|
+
(msg) => this.parseGChatListMessage(msg, spaceName, threadId)
|
|
1558
|
+
)
|
|
1559
|
+
);
|
|
1560
|
+
let nextCursor;
|
|
1561
|
+
if (startIndex + limit < allRawMessages.length && selectedMessages.length > 0) {
|
|
1562
|
+
const lastMsg = selectedMessages[selectedMessages.length - 1];
|
|
1563
|
+
if (lastMsg?.name) {
|
|
1564
|
+
nextCursor = lastMsg.name;
|
|
1565
|
+
}
|
|
1482
1566
|
}
|
|
1567
|
+
return {
|
|
1568
|
+
messages,
|
|
1569
|
+
nextCursor
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Parse a message from the list API into the standard Message format.
|
|
1574
|
+
* Resolves user display names and properly determines isMe.
|
|
1575
|
+
*/
|
|
1576
|
+
async parseGChatListMessage(msg, spaceName, _threadId) {
|
|
1577
|
+
const msgThreadId = this.encodeThreadId({
|
|
1578
|
+
spaceName,
|
|
1579
|
+
threadName: msg.thread?.name ?? void 0
|
|
1580
|
+
});
|
|
1581
|
+
const msgIsBot = msg.sender?.type === "BOT";
|
|
1582
|
+
const userId = msg.sender?.name || "unknown";
|
|
1583
|
+
const displayName = await this.resolveUserDisplayName(
|
|
1584
|
+
userId,
|
|
1585
|
+
msg.sender?.displayName ?? void 0
|
|
1586
|
+
);
|
|
1587
|
+
const isMe = this.isMessageFromSelf(msg);
|
|
1588
|
+
return {
|
|
1589
|
+
id: msg.name || "",
|
|
1590
|
+
threadId: msgThreadId,
|
|
1591
|
+
text: this.formatConverter.extractPlainText(msg.text || ""),
|
|
1592
|
+
formatted: this.formatConverter.toAst(msg.text || ""),
|
|
1593
|
+
raw: msg,
|
|
1594
|
+
author: {
|
|
1595
|
+
userId,
|
|
1596
|
+
userName: displayName,
|
|
1597
|
+
fullName: displayName,
|
|
1598
|
+
isBot: msgIsBot,
|
|
1599
|
+
isMe
|
|
1600
|
+
},
|
|
1601
|
+
metadata: {
|
|
1602
|
+
dateSent: msg.createTime ? new Date(msg.createTime) : /* @__PURE__ */ new Date(),
|
|
1603
|
+
edited: false
|
|
1604
|
+
},
|
|
1605
|
+
attachments: []
|
|
1606
|
+
};
|
|
1483
1607
|
}
|
|
1484
1608
|
async fetchThread(threadId) {
|
|
1485
1609
|
const { spaceName } = this.decodeThreadId(threadId);
|
|
@@ -1608,21 +1732,33 @@ var GoogleChatAdapter = class {
|
|
|
1608
1732
|
* Cache user info for later lookup (e.g., when processing Pub/Sub messages).
|
|
1609
1733
|
*/
|
|
1610
1734
|
async cacheUserInfo(userId, displayName, email) {
|
|
1611
|
-
if (!
|
|
1612
|
-
const
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1735
|
+
if (!displayName || displayName === "unknown") return;
|
|
1736
|
+
const userInfo = { displayName, email };
|
|
1737
|
+
this.userInfoCache.set(userId, userInfo);
|
|
1738
|
+
if (this.state) {
|
|
1739
|
+
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
1740
|
+
await this.state.set(
|
|
1741
|
+
cacheKey,
|
|
1742
|
+
userInfo,
|
|
1743
|
+
USER_INFO_CACHE_TTL_MS
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1618
1746
|
}
|
|
1619
1747
|
/**
|
|
1620
|
-
* Get cached user info.
|
|
1748
|
+
* Get cached user info. Checks in-memory cache first, then falls back to state adapter.
|
|
1621
1749
|
*/
|
|
1622
1750
|
async getCachedUserInfo(userId) {
|
|
1751
|
+
const inMemory = this.userInfoCache.get(userId);
|
|
1752
|
+
if (inMemory) {
|
|
1753
|
+
return inMemory;
|
|
1754
|
+
}
|
|
1623
1755
|
if (!this.state) return null;
|
|
1624
1756
|
const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
|
|
1625
|
-
|
|
1757
|
+
const fromState = await this.state.get(cacheKey);
|
|
1758
|
+
if (fromState) {
|
|
1759
|
+
this.userInfoCache.set(userId, fromState);
|
|
1760
|
+
}
|
|
1761
|
+
return fromState;
|
|
1626
1762
|
}
|
|
1627
1763
|
/**
|
|
1628
1764
|
* Resolve user display name, using cache if available.
|
|
@@ -1633,6 +1769,9 @@ var GoogleChatAdapter = class {
|
|
|
1633
1769
|
});
|
|
1634
1770
|
return providedDisplayName;
|
|
1635
1771
|
}
|
|
1772
|
+
if (this.botUserId && userId === this.botUserId) {
|
|
1773
|
+
return this.userName;
|
|
1774
|
+
}
|
|
1636
1775
|
const cached = await this.getCachedUserInfo(userId);
|
|
1637
1776
|
if (cached?.displayName) {
|
|
1638
1777
|
return cached.displayName;
|
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 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"]}
|
|
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 FetchResult,\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 /** In-memory user info cache for fast lookups */\n private userInfoCache = new Map<string, CachedUserInfo>();\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<FetchResult<unknown>> {\n const { spaceName, threadName } = this.decodeThreadId(threadId);\n const direction = options.direction ?? \"backward\";\n const limit = options.limit || 100;\n\n // Use impersonated client if available (has better permissions for listing messages)\n const api = this.impersonatedChatApi || this.chatApi;\n\n try {\n // Build filter to scope to specific thread if threadName is available\n const filter = threadName ? `thread.name = \"${threadName}\"` : undefined;\n\n if (direction === \"forward\") {\n // Forward direction: fetch oldest messages first\n // GChat API always returns newest first, so we need to work around this\n return this.fetchMessagesForward(\n api,\n spaceName,\n threadId,\n filter,\n limit,\n options.cursor,\n );\n }\n\n // Backward direction (default): most recent messages first\n // GChat API returns newest first, which matches this use case\n return this.fetchMessagesBackward(\n api,\n spaceName,\n threadId,\n filter,\n limit,\n options.cursor,\n );\n } catch (error) {\n this.handleGoogleChatError(error, \"fetchMessages\");\n }\n }\n\n /**\n * Fetch messages in backward direction (most recent first).\n * GChat API defaults to createTime ASC (oldest first), so we request DESC\n * to get the most recent messages, then reverse for chronological order within page.\n */\n private async fetchMessagesBackward(\n api: chat_v1.Chat,\n spaceName: string,\n threadId: string,\n filter: string | undefined,\n limit: number,\n cursor?: string,\n ): Promise<FetchResult<unknown>> {\n this.logger?.debug(\"GChat API: spaces.messages.list (backward)\", {\n spaceName,\n filter,\n pageSize: limit,\n cursor,\n });\n\n const response = await api.spaces.messages.list({\n parent: spaceName,\n pageSize: limit,\n pageToken: cursor,\n filter,\n orderBy: \"createTime desc\", // Get newest messages first\n });\n\n // API returns newest first (DESC), reverse to get chronological order within page\n const rawMessages = (response.data.messages || []).reverse();\n\n this.logger?.debug(\"GChat API: spaces.messages.list response (backward)\", {\n messageCount: rawMessages.length,\n hasNextPageToken: !!response.data.nextPageToken,\n });\n\n const messages = await Promise.all(\n rawMessages.map((msg) =>\n this.parseGChatListMessage(msg, spaceName, threadId),\n ),\n );\n\n return {\n messages,\n // nextPageToken points to older messages (backward pagination)\n nextCursor: response.data.nextPageToken ?? undefined,\n };\n }\n\n /**\n * Fetch messages in forward direction (oldest first).\n *\n * GChat API defaults to createTime ASC (oldest first), which is what we want.\n * For forward pagination, we:\n * 1. If no cursor: Fetch ALL messages (already in chronological order)\n * 2. If cursor: Cursor is a message name, skip to after that message\n *\n * Note: This is less efficient than backward for large message histories,\n * as it requires fetching all messages to find the cursor position.\n */\n private async fetchMessagesForward(\n api: chat_v1.Chat,\n spaceName: string,\n threadId: string,\n filter: string | undefined,\n limit: number,\n cursor?: string,\n ): Promise<FetchResult<unknown>> {\n this.logger?.debug(\"GChat API: spaces.messages.list (forward)\", {\n spaceName,\n filter,\n limit,\n cursor,\n });\n\n // Fetch all messages (GChat defaults to createTime ASC = oldest first)\n const allRawMessages: chat_v1.Schema$Message[] = [];\n let pageToken: string | undefined;\n\n do {\n const response = await api.spaces.messages.list({\n parent: spaceName,\n pageSize: 1000, // Max page size for efficiency\n pageToken,\n filter,\n // Default orderBy is createTime ASC (oldest first) - what we want\n });\n\n const pageMessages = response.data.messages || [];\n allRawMessages.push(...pageMessages);\n pageToken = response.data.nextPageToken ?? undefined;\n } while (pageToken);\n\n // Messages are already in chronological order (oldest first) from API\n\n this.logger?.debug(\n \"GChat API: fetched all messages for forward pagination\",\n {\n totalCount: allRawMessages.length,\n },\n );\n\n // Find starting position based on cursor\n let startIndex = 0;\n if (cursor) {\n // Cursor is a message name - find the index after it\n const cursorIndex = allRawMessages.findIndex(\n (msg) => msg.name === cursor,\n );\n if (cursorIndex >= 0) {\n startIndex = cursorIndex + 1;\n }\n }\n\n // Get the requested slice\n const selectedMessages = allRawMessages.slice(\n startIndex,\n startIndex + limit,\n );\n\n const messages = await Promise.all(\n selectedMessages.map((msg) =>\n this.parseGChatListMessage(msg, spaceName, threadId),\n ),\n );\n\n // Determine nextCursor - use message name of last returned message\n let nextCursor: string | undefined;\n if (\n startIndex + limit < allRawMessages.length &&\n selectedMessages.length > 0\n ) {\n const lastMsg = selectedMessages[selectedMessages.length - 1];\n if (lastMsg?.name) {\n nextCursor = lastMsg.name;\n }\n }\n\n return {\n messages,\n nextCursor,\n };\n }\n\n /**\n * Parse a message from the list API into the standard Message format.\n * Resolves user display names and properly determines isMe.\n */\n private async parseGChatListMessage(\n msg: chat_v1.Schema$Message,\n spaceName: string,\n _threadId: string,\n ): Promise<Message<unknown>> {\n const msgThreadId = this.encodeThreadId({\n spaceName,\n threadName: msg.thread?.name ?? undefined,\n });\n const msgIsBot = msg.sender?.type === \"BOT\";\n\n // Resolve display name - the list API may not include it\n const userId = msg.sender?.name || \"unknown\";\n const displayName = await this.resolveUserDisplayName(\n userId,\n msg.sender?.displayName ?? undefined,\n );\n\n // Use isMessageFromSelf for proper isMe determination\n const isMe = this.isMessageFromSelf(msg as GoogleChatMessage);\n\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,\n userName: displayName,\n fullName: displayName,\n isBot: msgIsBot,\n isMe,\n },\n metadata: {\n dateSent: msg.createTime ? new Date(msg.createTime) : new Date(),\n edited: false,\n },\n attachments: [],\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 (!displayName || displayName === \"unknown\") return;\n\n const userInfo: CachedUserInfo = { displayName, email };\n\n // Always update in-memory cache\n this.userInfoCache.set(userId, userInfo);\n\n // Also persist to state adapter if available\n if (this.state) {\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n await this.state.set<CachedUserInfo>(\n cacheKey,\n userInfo,\n USER_INFO_CACHE_TTL_MS,\n );\n }\n }\n\n /**\n * Get cached user info. Checks in-memory cache first, then falls back to state adapter.\n */\n private async getCachedUserInfo(\n userId: string,\n ): Promise<CachedUserInfo | null> {\n // Check in-memory cache first (fast path)\n const inMemory = this.userInfoCache.get(userId);\n if (inMemory) {\n return inMemory;\n }\n\n // Fall back to state adapter\n if (!this.state) return null;\n\n const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;\n const fromState = await this.state.get<CachedUserInfo>(cacheKey);\n\n // Populate in-memory cache if found in state\n if (fromState) {\n this.userInfoCache.set(userId, fromState);\n }\n\n return fromState;\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 // If this is our bot's user ID, use the configured bot name\n if (this.botUserId && userId === this.botUserId) {\n return this.userName;\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.
|
|
3
|
+
"version": "4.2.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.
|
|
20
|
+
"chat": "4.2.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.10.2",
|