@chat-adapter/gchat 4.2.0 → 4.3.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.js CHANGED
@@ -1,19 +1,21 @@
1
1
  // src/index.ts
2
2
  import {
3
- convertEmojiPlaceholders as convertEmojiPlaceholders2,
4
- defaultEmojiResolver,
5
- isCardElement,
6
- RateLimitError
7
- } from "chat";
3
+ AdapterRateLimitError,
4
+ AuthenticationError,
5
+ extractCard,
6
+ extractFiles,
7
+ NetworkError,
8
+ ValidationError as ValidationError2
9
+ } from "@chat-adapter/shared";
10
+ import { convertEmojiPlaceholders, defaultEmojiResolver } from "chat";
8
11
  import { google as google2 } from "googleapis";
9
12
 
10
13
  // src/cards.ts
11
14
  import {
12
- convertEmojiPlaceholders
13
- } from "chat";
14
- function convertEmoji(text) {
15
- return convertEmojiPlaceholders(text, "gchat");
16
- }
15
+ createEmojiConverter,
16
+ cardToFallbackText as sharedCardToFallbackText
17
+ } from "@chat-adapter/shared";
18
+ var convertEmoji = createEmojiConverter("gchat");
17
19
  function cardToGoogleCard(card, options) {
18
20
  const opts = typeof options === "string" ? { cardId: options } : options || {};
19
21
  const sections = [];
@@ -154,39 +156,29 @@ function convertFieldsToWidgets(element) {
154
156
  }));
155
157
  }
156
158
  function cardToFallbackText(card) {
157
- const parts = [];
158
- if (card.title) {
159
- parts.push(`*${convertEmoji(card.title)}*`);
160
- }
161
- if (card.subtitle) {
162
- parts.push(convertEmoji(card.subtitle));
163
- }
164
- for (const child of card.children) {
165
- const text = childToFallbackText(child);
166
- if (text) {
167
- parts.push(text);
168
- }
169
- }
170
- return parts.join("\n");
171
- }
172
- function childToFallbackText(child) {
173
- switch (child.type) {
174
- case "text":
175
- return convertEmoji(child.content);
176
- case "fields":
177
- return child.children.map((f) => `*${convertEmoji(f.label)}*: ${convertEmoji(f.value)}`).join("\n");
178
- case "actions":
179
- return `[${child.children.map((b) => convertEmoji(b.label)).join("] [")}]`;
180
- case "section":
181
- return child.children.map((c) => childToFallbackText(c)).filter(Boolean).join("\n");
182
- default:
183
- return null;
184
- }
159
+ return sharedCardToFallbackText(card, {
160
+ boldFormat: "*",
161
+ lineBreak: "\n",
162
+ platform: "gchat"
163
+ });
185
164
  }
186
165
 
187
166
  // src/markdown.ts
188
167
  import {
189
168
  BaseFormatConverter,
169
+ getNodeChildren,
170
+ getNodeValue,
171
+ isBlockquoteNode,
172
+ isCodeNode,
173
+ isDeleteNode,
174
+ isEmphasisNode,
175
+ isInlineCodeNode,
176
+ isLinkNode,
177
+ isListItemNode,
178
+ isListNode,
179
+ isParagraphNode,
180
+ isStrongNode,
181
+ isTextNode,
190
182
  parseMarkdown
191
183
  } from "chat";
192
184
  var GoogleChatFormatConverter = class extends BaseFormatConverter {
@@ -194,11 +186,7 @@ var GoogleChatFormatConverter = class extends BaseFormatConverter {
194
186
  * Render an AST to Google Chat format.
195
187
  */
196
188
  fromAst(ast) {
197
- const parts = [];
198
- for (const node of ast.children) {
199
- parts.push(this.nodeToGChat(node));
200
- }
201
- return parts.join("\n\n");
189
+ return this.fromAstWithNodeConverter(ast, (node) => this.nodeToGChat(node));
202
190
  }
203
191
  /**
204
192
  * Parse Google Chat message into an AST.
@@ -210,57 +198,155 @@ var GoogleChatFormatConverter = class extends BaseFormatConverter {
210
198
  return parseMarkdown(markdown);
211
199
  }
212
200
  nodeToGChat(node) {
213
- switch (node.type) {
214
- case "paragraph":
215
- return node.children.map((child) => this.nodeToGChat(child)).join("");
216
- case "text": {
217
- return node.value;
218
- }
219
- case "strong":
220
- return `*${node.children.map((child) => this.nodeToGChat(child)).join("")}*`;
221
- case "emphasis":
222
- return `_${node.children.map((child) => this.nodeToGChat(child)).join("")}_`;
223
- case "delete":
224
- return `~${node.children.map((child) => this.nodeToGChat(child)).join("")}~`;
225
- case "inlineCode":
226
- return `\`${node.value}\``;
227
- case "code": {
228
- const codeNode = node;
229
- return `\`\`\`
230
- ${codeNode.value}
201
+ if (isParagraphNode(node)) {
202
+ return getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
203
+ }
204
+ if (isTextNode(node)) {
205
+ return node.value;
206
+ }
207
+ if (isStrongNode(node)) {
208
+ const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
209
+ return `*${content}*`;
210
+ }
211
+ if (isEmphasisNode(node)) {
212
+ const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
213
+ return `_${content}_`;
214
+ }
215
+ if (isDeleteNode(node)) {
216
+ const content = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
217
+ return `~${content}~`;
218
+ }
219
+ if (isInlineCodeNode(node)) {
220
+ return `\`${node.value}\``;
221
+ }
222
+ if (isCodeNode(node)) {
223
+ return `\`\`\`
224
+ ${node.value}
231
225
  \`\`\``;
226
+ }
227
+ if (isLinkNode(node)) {
228
+ const linkText = getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
229
+ if (linkText === node.url) {
230
+ return node.url;
232
231
  }
233
- case "link": {
234
- const linkNode = node;
235
- const linkText = linkNode.children.map((child) => this.nodeToGChat(child)).join("");
236
- if (linkText === linkNode.url) {
237
- return linkNode.url;
238
- }
239
- return `${linkText} (${linkNode.url})`;
240
- }
241
- case "blockquote":
242
- return node.children.map((child) => `> ${this.nodeToGChat(child)}`).join("\n");
243
- case "list":
244
- return node.children.map((item, i) => {
245
- const prefix = node.ordered ? `${i + 1}.` : "\u2022";
246
- const content = item.children.map((child) => this.nodeToGChat(child)).join("");
247
- return `${prefix} ${content}`;
248
- }).join("\n");
249
- case "listItem":
250
- return node.children.map((child) => this.nodeToGChat(child)).join("");
251
- case "break":
252
- return "\n";
253
- case "thematicBreak":
254
- return "---";
255
- default:
256
- if ("children" in node && Array.isArray(node.children)) {
257
- return node.children.map((child) => this.nodeToGChat(child)).join("");
258
- }
259
- if ("value" in node) {
260
- return String(node.value);
261
- }
262
- return "";
232
+ return `${linkText} (${node.url})`;
233
+ }
234
+ if (isBlockquoteNode(node)) {
235
+ return getNodeChildren(node).map((child) => `> ${this.nodeToGChat(child)}`).join("\n");
236
+ }
237
+ if (isListNode(node)) {
238
+ return getNodeChildren(node).map((item, i) => {
239
+ const prefix = node.ordered ? `${i + 1}.` : "\u2022";
240
+ const content = getNodeChildren(item).map((child) => this.nodeToGChat(child)).join("");
241
+ return `${prefix} ${content}`;
242
+ }).join("\n");
243
+ }
244
+ if (isListItemNode(node)) {
245
+ return getNodeChildren(node).map((child) => this.nodeToGChat(child)).join("");
246
+ }
247
+ if (node.type === "break") {
248
+ return "\n";
249
+ }
250
+ if (node.type === "thematicBreak") {
251
+ return "---";
252
+ }
253
+ const children = getNodeChildren(node);
254
+ if (children.length > 0) {
255
+ return children.map((child) => this.nodeToGChat(child)).join("");
256
+ }
257
+ return getNodeValue(node);
258
+ }
259
+ };
260
+
261
+ // src/thread-utils.ts
262
+ import { ValidationError } from "@chat-adapter/shared";
263
+ function encodeThreadId(platformData) {
264
+ const threadPart = platformData.threadName ? `:${Buffer.from(platformData.threadName).toString("base64url")}` : "";
265
+ const dmPart = platformData.isDM ? ":dm" : "";
266
+ return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;
267
+ }
268
+ function decodeThreadId(threadId) {
269
+ const isDM = threadId.endsWith(":dm");
270
+ const cleanId = isDM ? threadId.slice(0, -3) : threadId;
271
+ const parts = cleanId.split(":");
272
+ if (parts.length < 2 || parts[0] !== "gchat") {
273
+ throw new ValidationError(
274
+ "gchat",
275
+ `Invalid Google Chat thread ID: ${threadId}`
276
+ );
277
+ }
278
+ const spaceName = parts[1];
279
+ const threadName = parts[2] ? Buffer.from(parts[2], "base64url").toString("utf-8") : void 0;
280
+ return { spaceName, threadName, isDM };
281
+ }
282
+ function isDMThread(threadId) {
283
+ return threadId.endsWith(":dm");
284
+ }
285
+
286
+ // src/user-info.ts
287
+ var USER_INFO_KEY_PREFIX = "gchat:user:";
288
+ var USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
289
+ var UserInfoCache = class {
290
+ constructor(state, logger) {
291
+ this.state = state;
292
+ this.logger = logger;
293
+ }
294
+ inMemoryCache = /* @__PURE__ */ new Map();
295
+ /**
296
+ * Cache user info for later lookup.
297
+ */
298
+ async set(userId, displayName, email) {
299
+ if (!displayName || displayName === "unknown") return;
300
+ const userInfo = { displayName, email };
301
+ this.inMemoryCache.set(userId, userInfo);
302
+ if (this.state) {
303
+ const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
304
+ await this.state.set(
305
+ cacheKey,
306
+ userInfo,
307
+ USER_INFO_CACHE_TTL_MS
308
+ );
309
+ }
310
+ }
311
+ /**
312
+ * Get cached user info. Checks in-memory cache first, then falls back to state adapter.
313
+ */
314
+ async get(userId) {
315
+ const inMemory = this.inMemoryCache.get(userId);
316
+ if (inMemory) {
317
+ return inMemory;
318
+ }
319
+ if (!this.state) return null;
320
+ const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
321
+ const fromState = await this.state.get(cacheKey);
322
+ if (fromState) {
323
+ this.inMemoryCache.set(userId, fromState);
324
+ }
325
+ return fromState;
326
+ }
327
+ /**
328
+ * Resolve user display name, using cache if available.
329
+ *
330
+ * @param userId - The user's resource name (e.g., "users/123456")
331
+ * @param providedDisplayName - Display name from the message if available
332
+ * @param botUserId - The bot's user ID (for self-identification)
333
+ * @param botUserName - The bot's configured username
334
+ */
335
+ async resolveDisplayName(userId, providedDisplayName, botUserId, botUserName) {
336
+ if (providedDisplayName && providedDisplayName !== "unknown") {
337
+ this.set(userId, providedDisplayName).catch((err) => {
338
+ this.logger.error("Failed to cache user info", { userId, error: err });
339
+ });
340
+ return providedDisplayName;
341
+ }
342
+ if (botUserId && userId === botUserId) {
343
+ return botUserName;
263
344
  }
345
+ const cached = await this.get(userId);
346
+ if (cached?.displayName) {
347
+ return cached.displayName;
348
+ }
349
+ return userId.replace("users/", "User ");
264
350
  }
265
351
  };
266
352
 
@@ -409,8 +495,6 @@ function verifyPubSubRequest(request, _expectedAudience) {
409
495
  var SUBSCRIPTION_REFRESH_BUFFER_MS = 60 * 60 * 1e3;
410
496
  var SUBSCRIPTION_CACHE_TTL_MS = 25 * 60 * 60 * 1e3;
411
497
  var SPACE_SUB_KEY_PREFIX = "gchat:space-sub:";
412
- var USER_INFO_KEY_PREFIX = "gchat:user:";
413
- var USER_INFO_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
414
498
  var GoogleChatAdapter = class {
415
499
  name = "gchat";
416
500
  userName;
@@ -419,7 +503,7 @@ var GoogleChatAdapter = class {
419
503
  chatApi;
420
504
  chat = null;
421
505
  state = null;
422
- logger = null;
506
+ logger;
423
507
  formatConverter = new GoogleChatFormatConverter();
424
508
  pubsubTopic;
425
509
  credentials;
@@ -436,10 +520,12 @@ var GoogleChatAdapter = class {
436
520
  impersonatedChatApi;
437
521
  /** HTTP endpoint URL for button click actions */
438
522
  endpointUrl;
439
- /** In-memory user info cache for fast lookups */
440
- userInfoCache = /* @__PURE__ */ new Map();
523
+ /** User info cache for display name lookups - initialized later in initialize() */
524
+ userInfoCache;
441
525
  constructor(config) {
526
+ this.logger = config.logger;
442
527
  this.userName = config.userName || "bot";
528
+ this.userInfoCache = new UserInfoCache(null, this.logger);
443
529
  this.pubsubTopic = config.pubsubTopic;
444
530
  this.impersonateUser = config.impersonateUser;
445
531
  this.endpointUrl = config.endpointUrl;
@@ -467,7 +553,8 @@ var GoogleChatAdapter = class {
467
553
  this.customAuth = config.auth;
468
554
  auth = config.auth;
469
555
  } else {
470
- throw new Error(
556
+ throw new ValidationError2(
557
+ "gchat",
471
558
  "GoogleChatAdapter requires one of: credentials, useApplicationDefaultCredentials, or auth"
472
559
  );
473
560
  }
@@ -510,12 +597,12 @@ var GoogleChatAdapter = class {
510
597
  async initialize(chat) {
511
598
  this.chat = chat;
512
599
  this.state = chat.getState();
513
- this.logger = chat.getLogger(this.name);
600
+ this.userInfoCache = new UserInfoCache(this.state, this.logger);
514
601
  if (!this.botUserId) {
515
602
  const savedBotUserId = await this.state.get("gchat:botUserId");
516
603
  if (savedBotUserId) {
517
604
  this.botUserId = savedBotUserId;
518
- this.logger?.debug("Restored bot user ID from state", {
605
+ this.logger.debug("Restored bot user ID from state", {
519
606
  botUserId: this.botUserId
520
607
  });
521
608
  }
@@ -526,13 +613,13 @@ var GoogleChatAdapter = class {
526
613
  * Ensures the space has a Workspace Events subscription so we receive all messages.
527
614
  */
528
615
  async onThreadSubscribe(threadId) {
529
- this.logger?.info("onThreadSubscribe called", {
616
+ this.logger.info("onThreadSubscribe called", {
530
617
  threadId,
531
618
  hasPubsubTopic: !!this.pubsubTopic,
532
619
  pubsubTopic: this.pubsubTopic
533
620
  });
534
621
  if (!this.pubsubTopic) {
535
- this.logger?.warn(
622
+ this.logger.warn(
536
623
  "No pubsubTopic configured, skipping space subscription. Set GOOGLE_CHAT_PUBSUB_TOPIC env var."
537
624
  );
538
625
  return;
@@ -545,7 +632,7 @@ var GoogleChatAdapter = class {
545
632
  * Creates one if it doesn't exist or is about to expire.
546
633
  */
547
634
  async ensureSpaceSubscription(spaceName) {
548
- this.logger?.info("ensureSpaceSubscription called", {
635
+ this.logger.info("ensureSpaceSubscription called", {
549
636
  spaceName,
550
637
  hasPubsubTopic: !!this.pubsubTopic,
551
638
  hasState: !!this.state,
@@ -553,7 +640,7 @@ var GoogleChatAdapter = class {
553
640
  hasADC: this.useADC
554
641
  });
555
642
  if (!this.pubsubTopic || !this.state) {
556
- this.logger?.warn("ensureSpaceSubscription skipped - missing config", {
643
+ this.logger.warn("ensureSpaceSubscription skipped - missing config", {
557
644
  hasPubsubTopic: !!this.pubsubTopic,
558
645
  hasState: !!this.state
559
646
  });
@@ -564,20 +651,20 @@ var GoogleChatAdapter = class {
564
651
  if (cached) {
565
652
  const timeUntilExpiry = cached.expireTime - Date.now();
566
653
  if (timeUntilExpiry > SUBSCRIPTION_REFRESH_BUFFER_MS) {
567
- this.logger?.debug("Space subscription still valid", {
654
+ this.logger.debug("Space subscription still valid", {
568
655
  spaceName,
569
656
  expiresIn: Math.round(timeUntilExpiry / 1e3 / 60)
570
657
  });
571
658
  return;
572
659
  }
573
- this.logger?.debug("Space subscription expiring soon, will refresh", {
660
+ this.logger.debug("Space subscription expiring soon, will refresh", {
574
661
  spaceName,
575
662
  expiresIn: Math.round(timeUntilExpiry / 1e3 / 60)
576
663
  });
577
664
  }
578
665
  const pending = this.pendingSubscriptions.get(spaceName);
579
666
  if (pending) {
580
- this.logger?.debug("Subscription creation already in progress", {
667
+ this.logger.debug("Subscription creation already in progress", {
581
668
  spaceName
582
669
  });
583
670
  return pending;
@@ -598,14 +685,14 @@ var GoogleChatAdapter = class {
598
685
  */
599
686
  async createSpaceSubscriptionWithCache(spaceName, cacheKey) {
600
687
  const authOptions = this.getAuthOptions();
601
- this.logger?.info("createSpaceSubscriptionWithCache", {
688
+ this.logger.info("createSpaceSubscriptionWithCache", {
602
689
  spaceName,
603
690
  hasAuthOptions: !!authOptions,
604
691
  hasCredentials: !!this.credentials,
605
692
  hasADC: this.useADC
606
693
  });
607
694
  if (!authOptions) {
608
- this.logger?.error(
695
+ this.logger.error(
609
696
  "Cannot create subscription: no auth configured. Use GOOGLE_CHAT_CREDENTIALS, GOOGLE_CHAT_USE_ADC=true, or custom auth."
610
697
  );
611
698
  return;
@@ -618,7 +705,7 @@ var GoogleChatAdapter = class {
618
705
  authOptions
619
706
  );
620
707
  if (existing) {
621
- this.logger?.debug("Found existing subscription", {
708
+ this.logger.debug("Found existing subscription", {
622
709
  spaceName,
623
710
  subscriptionName: existing.subscriptionName
624
711
  });
@@ -631,7 +718,7 @@ var GoogleChatAdapter = class {
631
718
  }
632
719
  return;
633
720
  }
634
- this.logger?.info("Creating Workspace Events subscription", {
721
+ this.logger.info("Creating Workspace Events subscription", {
635
722
  spaceName,
636
723
  pubsubTopic
637
724
  });
@@ -650,13 +737,13 @@ var GoogleChatAdapter = class {
650
737
  SUBSCRIPTION_CACHE_TTL_MS
651
738
  );
652
739
  }
653
- this.logger?.info("Workspace Events subscription created", {
740
+ this.logger.info("Workspace Events subscription created", {
654
741
  spaceName,
655
742
  subscriptionName: result.name,
656
743
  expireTime: result.expireTime
657
744
  });
658
745
  } catch (error) {
659
- this.logger?.error("Failed to create Workspace Events subscription", {
746
+ this.logger.error("Failed to create Workspace Events subscription", {
660
747
  spaceName,
661
748
  error
662
749
  });
@@ -681,7 +768,7 @@ var GoogleChatAdapter = class {
681
768
  }
682
769
  }
683
770
  } catch (error) {
684
- this.logger?.debug("Error checking existing subscriptions", { error });
771
+ this.logger.error("Error checking existing subscriptions", { error });
685
772
  }
686
773
  return null;
687
774
  }
@@ -711,14 +798,14 @@ var GoogleChatAdapter = class {
711
798
  try {
712
799
  const url = new URL(request.url);
713
800
  this.endpointUrl = url.toString();
714
- this.logger?.debug("Auto-detected endpoint URL", {
801
+ this.logger.debug("Auto-detected endpoint URL", {
715
802
  endpointUrl: this.endpointUrl
716
803
  });
717
804
  } catch {
718
805
  }
719
806
  }
720
807
  const body = await request.text();
721
- this.logger?.debug("GChat webhook raw body", { body });
808
+ this.logger.debug("GChat webhook raw body", { body });
722
809
  let parsed;
723
810
  try {
724
811
  parsed = JSON.parse(body);
@@ -732,7 +819,7 @@ var GoogleChatAdapter = class {
732
819
  const event = parsed;
733
820
  const addedPayload = event.chat?.addedToSpacePayload;
734
821
  if (addedPayload) {
735
- this.logger?.debug("Bot added to space", {
822
+ this.logger.debug("Bot added to space", {
736
823
  space: addedPayload.space.name,
737
824
  spaceType: addedPayload.space.type
738
825
  });
@@ -740,7 +827,7 @@ var GoogleChatAdapter = class {
740
827
  }
741
828
  const removedPayload = event.chat?.removedFromSpacePayload;
742
829
  if (removedPayload) {
743
- this.logger?.debug("Bot removed from space", {
830
+ this.logger.debug("Bot removed from space", {
744
831
  space: removedPayload.space.name
745
832
  });
746
833
  }
@@ -754,14 +841,14 @@ var GoogleChatAdapter = class {
754
841
  }
755
842
  const messagePayload = event.chat?.messagePayload;
756
843
  if (messagePayload) {
757
- this.logger?.debug("message event", {
844
+ this.logger.debug("message event", {
758
845
  space: messagePayload.space.name,
759
846
  sender: messagePayload.message.sender?.displayName,
760
847
  text: messagePayload.message.text?.slice(0, 50)
761
848
  });
762
849
  this.handleMessageEvent(event, options);
763
850
  } else if (!addedPayload && !removedPayload) {
764
- this.logger?.debug("Non-message event received", {
851
+ this.logger.debug("Non-message event received", {
765
852
  hasChat: !!event.chat,
766
853
  hasCommonEventObject: !!event.commonEventObject
767
854
  });
@@ -782,14 +869,14 @@ var GoogleChatAdapter = class {
782
869
  "google.workspace.chat.reaction.v1.deleted"
783
870
  ];
784
871
  if (eventType && !allowedEventTypes.includes(eventType)) {
785
- this.logger?.debug("Skipping unsupported Pub/Sub event", { eventType });
872
+ this.logger.debug("Skipping unsupported Pub/Sub event", { eventType });
786
873
  return new Response(JSON.stringify({ success: true }), {
787
874
  headers: { "Content-Type": "application/json" }
788
875
  });
789
876
  }
790
877
  try {
791
878
  const notification = decodePubSubMessage(pushMessage);
792
- this.logger?.debug("Pub/Sub notification decoded", {
879
+ this.logger.debug("Pub/Sub notification decoded", {
793
880
  eventType: notification.eventType,
794
881
  messageId: notification.message?.name,
795
882
  reactionName: notification.reaction?.name
@@ -804,7 +891,7 @@ var GoogleChatAdapter = class {
804
891
  headers: { "Content-Type": "application/json" }
805
892
  });
806
893
  } catch (error) {
807
- this.logger?.error("Error processing Pub/Sub message", { error });
894
+ this.logger.error("Error processing Pub/Sub message", { error });
808
895
  return new Response(JSON.stringify({ error: "Processing failed" }), {
809
896
  status: 200,
810
897
  headers: { "Content-Type": "application/json" }
@@ -832,7 +919,10 @@ var GoogleChatAdapter = class {
832
919
  if (resolvedSpaceName && options?.waitUntil) {
833
920
  options.waitUntil(
834
921
  this.ensureSpaceSubscription(resolvedSpaceName).catch((err) => {
835
- this.logger?.debug("Subscription refresh failed", { error: err });
922
+ this.logger.error("Subscription refresh failed", {
923
+ spaceName: resolvedSpaceName,
924
+ error: err
925
+ });
836
926
  })
837
927
  );
838
928
  }
@@ -878,13 +968,13 @@ var GoogleChatAdapter = class {
878
968
  spaceName: spaceName || "",
879
969
  threadName: threadName ?? void 0
880
970
  });
881
- this.logger?.debug("Fetched thread context for reaction", {
971
+ this.logger.debug("Fetched thread context for reaction", {
882
972
  messageName,
883
973
  threadName,
884
974
  threadId
885
975
  });
886
976
  } catch (error) {
887
- this.logger?.warn("Failed to fetch message for thread context", {
977
+ this.logger.warn("Failed to fetch message for thread context", {
888
978
  messageName,
889
979
  error
890
980
  });
@@ -928,15 +1018,17 @@ var GoogleChatAdapter = class {
928
1018
  async parsePubSubMessage(notification, threadId) {
929
1019
  const message = notification.message;
930
1020
  if (!message) {
931
- throw new Error("PubSub notification missing message");
1021
+ throw new ValidationError2("gchat", "PubSub notification missing message");
932
1022
  }
933
1023
  const text = this.normalizeBotMentions(message);
934
1024
  const isBot = message.sender?.type === "BOT";
935
1025
  const isMe = this.isMessageFromSelf(message);
936
1026
  const userId = message.sender?.name || "unknown";
937
- const displayName = await this.resolveUserDisplayName(
1027
+ const displayName = await this.userInfoCache.resolveDisplayName(
938
1028
  userId,
939
- message.sender?.displayName
1029
+ message.sender?.displayName,
1030
+ this.botUserId,
1031
+ this.userName
940
1032
  );
941
1033
  const parsedMessage = {
942
1034
  id: message.name,
@@ -959,7 +1051,7 @@ var GoogleChatAdapter = class {
959
1051
  (att) => this.createAttachment(att)
960
1052
  )
961
1053
  };
962
- this.logger?.debug("Pub/Sub parsed message", {
1054
+ this.logger.debug("Pub/Sub parsed message", {
963
1055
  threadId,
964
1056
  messageId: parsedMessage.id,
965
1057
  text: parsedMessage.text,
@@ -985,14 +1077,14 @@ var GoogleChatAdapter = class {
985
1077
  */
986
1078
  handleCardClick(event, options) {
987
1079
  if (!this.chat) {
988
- this.logger?.warn("Chat instance not initialized, ignoring card click");
1080
+ this.logger.warn("Chat instance not initialized, ignoring card click");
989
1081
  return;
990
1082
  }
991
1083
  const buttonPayload = event.chat?.buttonClickedPayload;
992
1084
  const commonEvent = event.commonEventObject;
993
1085
  const actionId = commonEvent?.parameters?.actionId || commonEvent?.invokedFunction;
994
1086
  if (!actionId) {
995
- this.logger?.debug("Card click missing actionId", {
1087
+ this.logger.debug("Card click missing actionId", {
996
1088
  parameters: commonEvent?.parameters,
997
1089
  invokedFunction: commonEvent?.invokedFunction
998
1090
  });
@@ -1003,7 +1095,7 @@ var GoogleChatAdapter = class {
1003
1095
  const message = buttonPayload?.message;
1004
1096
  const user = buttonPayload?.user || event.chat?.user;
1005
1097
  if (!space) {
1006
- this.logger?.warn("Card click missing space info");
1098
+ this.logger.warn("Card click missing space info");
1007
1099
  return;
1008
1100
  }
1009
1101
  const threadName = message?.thread?.name || message?.name;
@@ -1026,7 +1118,7 @@ var GoogleChatAdapter = class {
1026
1118
  adapter: this,
1027
1119
  raw: event
1028
1120
  };
1029
- this.logger?.debug("Processing GChat card click", {
1121
+ this.logger.debug("Processing GChat card click", {
1030
1122
  actionId,
1031
1123
  value,
1032
1124
  messageId: actionEvent.messageId,
@@ -1039,12 +1131,12 @@ var GoogleChatAdapter = class {
1039
1131
  */
1040
1132
  handleMessageEvent(event, options) {
1041
1133
  if (!this.chat) {
1042
- this.logger?.warn("Chat instance not initialized, ignoring event");
1134
+ this.logger.warn("Chat instance not initialized, ignoring event");
1043
1135
  return;
1044
1136
  }
1045
1137
  const messagePayload = event.chat?.messagePayload;
1046
1138
  if (!messagePayload) {
1047
- this.logger?.debug("Ignoring event without messagePayload");
1139
+ this.logger.debug("Ignoring event without messagePayload");
1048
1140
  return;
1049
1141
  }
1050
1142
  const message = messagePayload.message;
@@ -1065,7 +1157,7 @@ var GoogleChatAdapter = class {
1065
1157
  parseGoogleChatMessage(event, threadId) {
1066
1158
  const message = event.chat?.messagePayload?.message;
1067
1159
  if (!message) {
1068
- throw new Error("Event has no message payload");
1160
+ throw new ValidationError2("gchat", "Event has no message payload");
1069
1161
  }
1070
1162
  const text = this.normalizeBotMentions(message);
1071
1163
  const isBot = message.sender?.type === "BOT";
@@ -1073,10 +1165,9 @@ var GoogleChatAdapter = class {
1073
1165
  const userId = message.sender?.name || "unknown";
1074
1166
  const displayName = message.sender?.displayName || "unknown";
1075
1167
  if (userId !== "unknown" && displayName !== "unknown") {
1076
- this.cacheUserInfo(userId, displayName, message.sender?.email).catch(
1077
- () => {
1078
- }
1079
- );
1168
+ this.userInfoCache.set(userId, displayName, message.sender?.email).catch((error) => {
1169
+ this.logger.error("Failed to cache user info", { userId, error });
1170
+ });
1080
1171
  }
1081
1172
  return {
1082
1173
  id: message.name,
@@ -1103,21 +1194,21 @@ var GoogleChatAdapter = class {
1103
1194
  async postMessage(threadId, message) {
1104
1195
  const { spaceName, threadName } = this.decodeThreadId(threadId);
1105
1196
  try {
1106
- const files = this.extractFiles(message);
1197
+ const files = extractFiles(message);
1107
1198
  if (files.length > 0) {
1108
- this.logger?.warn(
1199
+ this.logger.warn(
1109
1200
  "File uploads are not yet supported for Google Chat. Files will be ignored.",
1110
1201
  { fileCount: files.length }
1111
1202
  );
1112
1203
  }
1113
- const card = this.extractCard(message);
1204
+ const card = extractCard(message);
1114
1205
  if (card) {
1115
1206
  const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1116
1207
  const googleCard = cardToGoogleCard(card, {
1117
1208
  cardId,
1118
1209
  endpointUrl: this.endpointUrl
1119
1210
  });
1120
- this.logger?.debug("GChat API: spaces.messages.create (card)", {
1211
+ this.logger.debug("GChat API: spaces.messages.create (card)", {
1121
1212
  spaceName,
1122
1213
  threadName,
1123
1214
  googleCard: JSON.stringify(googleCard)
@@ -1131,7 +1222,7 @@ var GoogleChatAdapter = class {
1131
1222
  thread: threadName ? { name: threadName } : void 0
1132
1223
  }
1133
1224
  });
1134
- this.logger?.debug("GChat API: spaces.messages.create response", {
1225
+ this.logger.debug("GChat API: spaces.messages.create response", {
1135
1226
  messageName: response2.data.name
1136
1227
  });
1137
1228
  return {
@@ -1140,11 +1231,11 @@ var GoogleChatAdapter = class {
1140
1231
  raw: response2.data
1141
1232
  };
1142
1233
  }
1143
- const text = convertEmojiPlaceholders2(
1234
+ const text = convertEmojiPlaceholders(
1144
1235
  this.formatConverter.renderPostable(message),
1145
1236
  "gchat"
1146
1237
  );
1147
- this.logger?.debug("GChat API: spaces.messages.create", {
1238
+ this.logger.debug("GChat API: spaces.messages.create", {
1148
1239
  spaceName,
1149
1240
  threadName,
1150
1241
  textLength: text.length
@@ -1158,7 +1249,7 @@ var GoogleChatAdapter = class {
1158
1249
  thread: threadName ? { name: threadName } : void 0
1159
1250
  }
1160
1251
  });
1161
- this.logger?.debug("GChat API: spaces.messages.create response", {
1252
+ this.logger.debug("GChat API: spaces.messages.create response", {
1162
1253
  messageName: response.data.name
1163
1254
  });
1164
1255
  return {
@@ -1170,27 +1261,6 @@ var GoogleChatAdapter = class {
1170
1261
  this.handleGoogleChatError(error, "postMessage");
1171
1262
  }
1172
1263
  }
1173
- /**
1174
- * Extract card element from a message if present.
1175
- */
1176
- extractCard(message) {
1177
- if (isCardElement(message)) {
1178
- return message;
1179
- }
1180
- if (typeof message === "object" && message !== null && "card" in message) {
1181
- return message.card;
1182
- }
1183
- return null;
1184
- }
1185
- /**
1186
- * Extract files from a message if present.
1187
- */
1188
- extractFiles(message) {
1189
- if (typeof message === "object" && message !== null && "files" in message) {
1190
- return message.files ?? [];
1191
- }
1192
- return [];
1193
- }
1194
1264
  /**
1195
1265
  * Create an Attachment object from a Google Chat attachment.
1196
1266
  */
@@ -1213,12 +1283,18 @@ var GoogleChatAdapter = class {
1213
1283
  mimeType: att.contentType || void 0,
1214
1284
  fetchData: url ? async () => {
1215
1285
  if (typeof auth === "string" || !auth) {
1216
- throw new Error("Cannot fetch file: no auth client configured");
1286
+ throw new AuthenticationError(
1287
+ "gchat",
1288
+ "Cannot fetch file: no auth client configured"
1289
+ );
1217
1290
  }
1218
1291
  const tokenResult = await auth.getAccessToken();
1219
1292
  const token = typeof tokenResult === "string" ? tokenResult : tokenResult?.token;
1220
1293
  if (!token) {
1221
- throw new Error("Failed to get access token");
1294
+ throw new AuthenticationError(
1295
+ "gchat",
1296
+ "Failed to get access token"
1297
+ );
1222
1298
  }
1223
1299
  const response = await fetch(url, {
1224
1300
  headers: {
@@ -1226,7 +1302,8 @@ var GoogleChatAdapter = class {
1226
1302
  }
1227
1303
  });
1228
1304
  if (!response.ok) {
1229
- throw new Error(
1305
+ throw new NetworkError(
1306
+ "gchat",
1230
1307
  `Failed to fetch file: ${response.status} ${response.statusText}`
1231
1308
  );
1232
1309
  }
@@ -1237,14 +1314,14 @@ var GoogleChatAdapter = class {
1237
1314
  }
1238
1315
  async editMessage(threadId, messageId, message) {
1239
1316
  try {
1240
- const card = this.extractCard(message);
1317
+ const card = extractCard(message);
1241
1318
  if (card) {
1242
1319
  const cardId = `card-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1243
1320
  const googleCard = cardToGoogleCard(card, {
1244
1321
  cardId,
1245
1322
  endpointUrl: this.endpointUrl
1246
1323
  });
1247
- this.logger?.debug("GChat API: spaces.messages.update (card)", {
1324
+ this.logger.debug("GChat API: spaces.messages.update (card)", {
1248
1325
  messageId,
1249
1326
  cardId
1250
1327
  });
@@ -1256,7 +1333,7 @@ var GoogleChatAdapter = class {
1256
1333
  cardsV2: [googleCard]
1257
1334
  }
1258
1335
  });
1259
- this.logger?.debug("GChat API: spaces.messages.update response", {
1336
+ this.logger.debug("GChat API: spaces.messages.update response", {
1260
1337
  messageName: response2.data.name
1261
1338
  });
1262
1339
  return {
@@ -1265,11 +1342,11 @@ var GoogleChatAdapter = class {
1265
1342
  raw: response2.data
1266
1343
  };
1267
1344
  }
1268
- const text = convertEmojiPlaceholders2(
1345
+ const text = convertEmojiPlaceholders(
1269
1346
  this.formatConverter.renderPostable(message),
1270
1347
  "gchat"
1271
1348
  );
1272
- this.logger?.debug("GChat API: spaces.messages.update", {
1349
+ this.logger.debug("GChat API: spaces.messages.update", {
1273
1350
  messageId,
1274
1351
  textLength: text.length
1275
1352
  });
@@ -1280,7 +1357,7 @@ var GoogleChatAdapter = class {
1280
1357
  text
1281
1358
  }
1282
1359
  });
1283
- this.logger?.debug("GChat API: spaces.messages.update response", {
1360
+ this.logger.debug("GChat API: spaces.messages.update response", {
1284
1361
  messageName: response.data.name
1285
1362
  });
1286
1363
  return {
@@ -1294,11 +1371,11 @@ var GoogleChatAdapter = class {
1294
1371
  }
1295
1372
  async deleteMessage(_threadId, messageId) {
1296
1373
  try {
1297
- this.logger?.debug("GChat API: spaces.messages.delete", { messageId });
1374
+ this.logger.debug("GChat API: spaces.messages.delete", { messageId });
1298
1375
  await this.chatApi.spaces.messages.delete({
1299
1376
  name: messageId
1300
1377
  });
1301
- this.logger?.debug("GChat API: spaces.messages.delete response", {
1378
+ this.logger.debug("GChat API: spaces.messages.delete response", {
1302
1379
  ok: true
1303
1380
  });
1304
1381
  } catch (error) {
@@ -1308,7 +1385,7 @@ var GoogleChatAdapter = class {
1308
1385
  async addReaction(_threadId, messageId, emoji) {
1309
1386
  const gchatEmoji = defaultEmojiResolver.toGChat(emoji);
1310
1387
  try {
1311
- this.logger?.debug("GChat API: spaces.messages.reactions.create", {
1388
+ this.logger.debug("GChat API: spaces.messages.reactions.create", {
1312
1389
  messageId,
1313
1390
  emoji: gchatEmoji
1314
1391
  });
@@ -1318,7 +1395,7 @@ var GoogleChatAdapter = class {
1318
1395
  emoji: { unicode: gchatEmoji }
1319
1396
  }
1320
1397
  });
1321
- this.logger?.debug(
1398
+ this.logger.debug(
1322
1399
  "GChat API: spaces.messages.reactions.create response",
1323
1400
  {
1324
1401
  ok: true
@@ -1331,32 +1408,32 @@ var GoogleChatAdapter = class {
1331
1408
  async removeReaction(_threadId, messageId, emoji) {
1332
1409
  const gchatEmoji = defaultEmojiResolver.toGChat(emoji);
1333
1410
  try {
1334
- this.logger?.debug("GChat API: spaces.messages.reactions.list", {
1411
+ this.logger.debug("GChat API: spaces.messages.reactions.list", {
1335
1412
  messageId
1336
1413
  });
1337
1414
  const response = await this.chatApi.spaces.messages.reactions.list({
1338
1415
  parent: messageId
1339
1416
  });
1340
- this.logger?.debug("GChat API: spaces.messages.reactions.list response", {
1417
+ this.logger.debug("GChat API: spaces.messages.reactions.list response", {
1341
1418
  reactionCount: response.data.reactions?.length || 0
1342
1419
  });
1343
1420
  const reaction = response.data.reactions?.find(
1344
1421
  (r) => r.emoji?.unicode === gchatEmoji
1345
1422
  );
1346
1423
  if (!reaction?.name) {
1347
- this.logger?.debug("Reaction not found to remove", {
1424
+ this.logger.debug("Reaction not found to remove", {
1348
1425
  messageId,
1349
1426
  emoji: gchatEmoji
1350
1427
  });
1351
1428
  return;
1352
1429
  }
1353
- this.logger?.debug("GChat API: spaces.messages.reactions.delete", {
1430
+ this.logger.debug("GChat API: spaces.messages.reactions.delete", {
1354
1431
  reactionName: reaction.name
1355
1432
  });
1356
1433
  await this.chatApi.spaces.messages.reactions.delete({
1357
1434
  name: reaction.name
1358
1435
  });
1359
- this.logger?.debug(
1436
+ this.logger.debug(
1360
1437
  "GChat API: spaces.messages.reactions.delete response",
1361
1438
  {
1362
1439
  ok: true
@@ -1379,12 +1456,12 @@ var GoogleChatAdapter = class {
1379
1456
  */
1380
1457
  async openDM(userId) {
1381
1458
  try {
1382
- this.logger?.debug("GChat API: spaces.findDirectMessage", { userId });
1459
+ this.logger.debug("GChat API: spaces.findDirectMessage", { userId });
1383
1460
  const findResponse = await this.chatApi.spaces.findDirectMessage({
1384
1461
  name: userId
1385
1462
  });
1386
1463
  if (findResponse.data.name) {
1387
- this.logger?.debug("GChat API: Found existing DM space", {
1464
+ this.logger.debug("GChat API: Found existing DM space", {
1388
1465
  spaceName: findResponse.data.name
1389
1466
  });
1390
1467
  return this.encodeThreadId({
@@ -1395,17 +1472,17 @@ var GoogleChatAdapter = class {
1395
1472
  } catch (error) {
1396
1473
  const gError = error;
1397
1474
  if (gError.code !== 404) {
1398
- this.logger?.debug("GChat API: findDirectMessage failed", { error });
1475
+ this.logger.debug("GChat API: findDirectMessage failed", { error });
1399
1476
  }
1400
1477
  }
1401
1478
  const chatApi = this.impersonatedChatApi || this.chatApi;
1402
1479
  if (!this.impersonatedChatApi) {
1403
- this.logger?.warn(
1480
+ this.logger.warn(
1404
1481
  "openDM: No existing DM found and no impersonation configured. Creating new DMs requires domain-wide delegation. Set 'impersonateUser' in adapter config."
1405
1482
  );
1406
1483
  }
1407
1484
  try {
1408
- this.logger?.debug("GChat API: spaces.setup (DM)", {
1485
+ this.logger.debug("GChat API: spaces.setup (DM)", {
1409
1486
  userId,
1410
1487
  hasImpersonation: !!this.impersonatedChatApi,
1411
1488
  impersonateUser: this.impersonateUser
@@ -1427,9 +1504,12 @@ var GoogleChatAdapter = class {
1427
1504
  });
1428
1505
  const spaceName = response.data.name;
1429
1506
  if (!spaceName) {
1430
- throw new Error("Failed to create DM - no space name returned");
1507
+ throw new NetworkError(
1508
+ "gchat",
1509
+ "Failed to create DM - no space name returned"
1510
+ );
1431
1511
  }
1432
- this.logger?.debug("GChat API: spaces.setup response", { spaceName });
1512
+ this.logger.debug("GChat API: spaces.setup response", { spaceName });
1433
1513
  return this.encodeThreadId({ spaceName, isDM: true });
1434
1514
  } catch (error) {
1435
1515
  this.handleGoogleChatError(error, "openDM");
@@ -1470,7 +1550,7 @@ var GoogleChatAdapter = class {
1470
1550
  * to get the most recent messages, then reverse for chronological order within page.
1471
1551
  */
1472
1552
  async fetchMessagesBackward(api, spaceName, threadId, filter, limit, cursor) {
1473
- this.logger?.debug("GChat API: spaces.messages.list (backward)", {
1553
+ this.logger.debug("GChat API: spaces.messages.list (backward)", {
1474
1554
  spaceName,
1475
1555
  filter,
1476
1556
  pageSize: limit,
@@ -1485,7 +1565,7 @@ var GoogleChatAdapter = class {
1485
1565
  // Get newest messages first
1486
1566
  });
1487
1567
  const rawMessages = (response.data.messages || []).reverse();
1488
- this.logger?.debug("GChat API: spaces.messages.list response (backward)", {
1568
+ this.logger.debug("GChat API: spaces.messages.list response (backward)", {
1489
1569
  messageCount: rawMessages.length,
1490
1570
  hasNextPageToken: !!response.data.nextPageToken
1491
1571
  });
@@ -1512,7 +1592,7 @@ var GoogleChatAdapter = class {
1512
1592
  * as it requires fetching all messages to find the cursor position.
1513
1593
  */
1514
1594
  async fetchMessagesForward(api, spaceName, threadId, filter, limit, cursor) {
1515
- this.logger?.debug("GChat API: spaces.messages.list (forward)", {
1595
+ this.logger.debug("GChat API: spaces.messages.list (forward)", {
1516
1596
  spaceName,
1517
1597
  filter,
1518
1598
  limit,
@@ -1533,7 +1613,7 @@ var GoogleChatAdapter = class {
1533
1613
  allRawMessages.push(...pageMessages);
1534
1614
  pageToken = response.data.nextPageToken ?? void 0;
1535
1615
  } while (pageToken);
1536
- this.logger?.debug(
1616
+ this.logger.debug(
1537
1617
  "GChat API: fetched all messages for forward pagination",
1538
1618
  {
1539
1619
  totalCount: allRawMessages.length
@@ -1580,9 +1660,11 @@ var GoogleChatAdapter = class {
1580
1660
  });
1581
1661
  const msgIsBot = msg.sender?.type === "BOT";
1582
1662
  const userId = msg.sender?.name || "unknown";
1583
- const displayName = await this.resolveUserDisplayName(
1663
+ const displayName = await this.userInfoCache.resolveDisplayName(
1584
1664
  userId,
1585
- msg.sender?.displayName ?? void 0
1665
+ msg.sender?.displayName ?? void 0,
1666
+ this.botUserId,
1667
+ this.userName
1586
1668
  );
1587
1669
  const isMe = this.isMessageFromSelf(msg);
1588
1670
  return {
@@ -1608,9 +1690,9 @@ var GoogleChatAdapter = class {
1608
1690
  async fetchThread(threadId) {
1609
1691
  const { spaceName } = this.decodeThreadId(threadId);
1610
1692
  try {
1611
- this.logger?.debug("GChat API: spaces.get", { spaceName });
1693
+ this.logger.debug("GChat API: spaces.get", { spaceName });
1612
1694
  const response = await this.chatApi.spaces.get({ name: spaceName });
1613
- this.logger?.debug("GChat API: spaces.get response", {
1695
+ this.logger.debug("GChat API: spaces.get response", {
1614
1696
  displayName: response.data.displayName
1615
1697
  });
1616
1698
  return {
@@ -1626,34 +1708,22 @@ var GoogleChatAdapter = class {
1626
1708
  }
1627
1709
  }
1628
1710
  encodeThreadId(platformData) {
1629
- const threadPart = platformData.threadName ? `:${Buffer.from(platformData.threadName).toString("base64url")}` : "";
1630
- const dmPart = platformData.isDM ? ":dm" : "";
1631
- return `gchat:${platformData.spaceName}${threadPart}${dmPart}`;
1711
+ return encodeThreadId(platformData);
1632
1712
  }
1633
1713
  /**
1634
1714
  * Check if a thread is a direct message conversation.
1635
- * Checks for the :dm marker in the thread ID which is set when
1636
- * processing DM messages or opening DMs.
1637
1715
  */
1638
1716
  isDM(threadId) {
1639
- return threadId.endsWith(":dm");
1717
+ return isDMThread(threadId);
1640
1718
  }
1641
1719
  decodeThreadId(threadId) {
1642
- const isDM = threadId.endsWith(":dm");
1643
- const cleanId = isDM ? threadId.slice(0, -3) : threadId;
1644
- const parts = cleanId.split(":");
1645
- if (parts.length < 2 || parts[0] !== "gchat") {
1646
- throw new Error(`Invalid Google Chat thread ID: ${threadId}`);
1647
- }
1648
- const spaceName = parts[1];
1649
- const threadName = parts[2] ? Buffer.from(parts[2], "base64url").toString("utf-8") : void 0;
1650
- return { spaceName, threadName, isDM };
1720
+ return decodeThreadId(threadId);
1651
1721
  }
1652
1722
  parseMessage(raw) {
1653
1723
  const event = raw;
1654
1724
  const messagePayload = event.chat?.messagePayload;
1655
1725
  if (!messagePayload) {
1656
- throw new Error("Cannot parse non-message event");
1726
+ throw new ValidationError2("gchat", "Cannot parse non-message event");
1657
1727
  }
1658
1728
  const threadName = messagePayload.message.thread?.name || messagePayload.message.name;
1659
1729
  const threadId = this.encodeThreadId({
@@ -1681,11 +1751,11 @@ var GoogleChatAdapter = class {
1681
1751
  const botDisplayName = botUser.displayName;
1682
1752
  if (botUser.name && !this.botUserId) {
1683
1753
  this.botUserId = botUser.name;
1684
- this.logger?.info("Learned bot user ID from mention", {
1754
+ this.logger.info("Learned bot user ID from mention", {
1685
1755
  botUserId: this.botUserId
1686
1756
  });
1687
1757
  this.state?.set("gchat:botUserId", this.botUserId).catch(
1688
- (err) => this.logger?.debug("Failed to persist botUserId", { error: err })
1758
+ (err) => this.logger.error("Failed to persist botUserId", { error: err })
1689
1759
  );
1690
1760
  }
1691
1761
  if (annotation.startIndex !== void 0 && annotation.length !== void 0) {
@@ -1693,7 +1763,7 @@ var GoogleChatAdapter = class {
1693
1763
  const length = annotation.length;
1694
1764
  const mentionText = text.slice(startIndex, startIndex + length);
1695
1765
  text = text.slice(0, startIndex) + `@${this.userName}` + text.slice(startIndex + length);
1696
- this.logger?.debug("Normalized bot mention", {
1766
+ this.logger.debug("Normalized bot mention", {
1697
1767
  original: mentionText,
1698
1768
  replacement: `@${this.userName}`
1699
1769
  });
@@ -1721,77 +1791,23 @@ var GoogleChatAdapter = class {
1721
1791
  return senderId === this.botUserId;
1722
1792
  }
1723
1793
  if (!this.botUserId && message.sender?.type === "BOT") {
1724
- this.logger?.debug(
1794
+ this.logger.debug(
1725
1795
  "Cannot determine isMe - bot user ID not yet learned. Bot ID is learned from @mentions. Assuming message is not from self.",
1726
1796
  { senderId }
1727
1797
  );
1728
1798
  }
1729
1799
  return false;
1730
1800
  }
1731
- /**
1732
- * Cache user info for later lookup (e.g., when processing Pub/Sub messages).
1733
- */
1734
- async cacheUserInfo(userId, displayName, email) {
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
- }
1746
- }
1747
- /**
1748
- * Get cached user info. Checks in-memory cache first, then falls back to state adapter.
1749
- */
1750
- async getCachedUserInfo(userId) {
1751
- const inMemory = this.userInfoCache.get(userId);
1752
- if (inMemory) {
1753
- return inMemory;
1754
- }
1755
- if (!this.state) return null;
1756
- const cacheKey = `${USER_INFO_KEY_PREFIX}${userId}`;
1757
- const fromState = await this.state.get(cacheKey);
1758
- if (fromState) {
1759
- this.userInfoCache.set(userId, fromState);
1760
- }
1761
- return fromState;
1762
- }
1763
- /**
1764
- * Resolve user display name, using cache if available.
1765
- */
1766
- async resolveUserDisplayName(userId, providedDisplayName) {
1767
- if (providedDisplayName && providedDisplayName !== "unknown") {
1768
- this.cacheUserInfo(userId, providedDisplayName).catch(() => {
1769
- });
1770
- return providedDisplayName;
1771
- }
1772
- if (this.botUserId && userId === this.botUserId) {
1773
- return this.userName;
1774
- }
1775
- const cached = await this.getCachedUserInfo(userId);
1776
- if (cached?.displayName) {
1777
- return cached.displayName;
1778
- }
1779
- return userId.replace("users/", "User ");
1780
- }
1781
1801
  handleGoogleChatError(error, context) {
1782
1802
  const gError = error;
1783
- this.logger?.error(`GChat API error${context ? ` (${context})` : ""}`, {
1803
+ this.logger.error(`GChat API error${context ? ` (${context})` : ""}`, {
1784
1804
  code: gError.code,
1785
1805
  message: gError.message,
1786
1806
  errors: gError.errors,
1787
1807
  error
1788
1808
  });
1789
1809
  if (gError.code === 429) {
1790
- throw new RateLimitError(
1791
- "Google Chat rate limit exceeded",
1792
- void 0,
1793
- error
1794
- );
1810
+ throw new AdapterRateLimitError("gchat");
1795
1811
  }
1796
1812
  throw error;
1797
1813
  }