@chat-adapter/gchat 4.13.0 → 4.13.2

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/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # @chat-adapter/gchat
2
2
 
3
- Google Chat adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
3
+ [![npm version](https://img.shields.io/npm/v/@chat-adapter/gchat)](https://www.npmjs.com/package/@chat-adapter/gchat)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/gchat)](https://www.npmjs.com/package/@chat-adapter/gchat)
5
+
6
+ Google Chat adapter for [Chat SDK](https://chat-sdk.dev/docs). Supports service account authentication with optional Pub/Sub for receiving all messages.
4
7
 
5
8
  ## Installation
6
9
 
@@ -14,7 +17,7 @@ npm install chat @chat-adapter/gchat
14
17
  import { Chat } from "chat";
15
18
  import { createGoogleChatAdapter } from "@chat-adapter/gchat";
16
19
 
17
- const chat = new Chat({
20
+ const bot = new Chat({
18
21
  userName: "mybot",
19
22
  adapters: {
20
23
  gchat: createGoogleChatAdapter({
@@ -23,222 +26,14 @@ const chat = new Chat({
23
26
  },
24
27
  });
25
28
 
26
- // Handle @mentions
27
- chat.onNewMention(async (thread, message) => {
29
+ bot.onNewMention(async (thread, message) => {
28
30
  await thread.post("Hello from Google Chat!");
29
31
  });
30
32
  ```
31
33
 
32
- ## Configuration
33
-
34
- | Option | Required | Description |
35
- |--------|----------|-------------|
36
- | `credentials` | Yes* | Service account credentials JSON |
37
- | `useADC` | No | Use Application Default Credentials instead |
38
- | `pubsubTopic` | No | Pub/Sub topic for Workspace Events |
39
- | `impersonateUser` | No | User email for domain-wide delegation |
40
-
41
- *Either `credentials` or `useADC: true` is required.
42
-
43
- ## Environment Variables
44
-
45
- ```bash
46
- GOOGLE_CHAT_CREDENTIALS={"type":"service_account",...}
47
-
48
- # Optional: for receiving ALL messages, not just @mentions
49
- GOOGLE_CHAT_PUBSUB_TOPIC=projects/your-project/topics/chat-events
50
- GOOGLE_CHAT_IMPERSONATE_USER=admin@yourdomain.com
51
- ```
52
-
53
- ## Google Chat Setup
54
-
55
- ### 1. Create a GCP Project
56
-
57
- 1. Go to [console.cloud.google.com](https://console.cloud.google.com)
58
- 2. Click the project dropdown → **New Project**
59
- 3. Enter project name and click **Create**
60
-
61
- ### 2. Enable Required APIs
62
-
63
- 1. Go to **APIs & Services** → **Library**
64
- 2. Search and enable:
65
- - **Google Chat API**
66
- - **Google Workspace Events API** (for receiving all messages)
67
- - **Cloud Pub/Sub API** (for receiving all messages)
68
-
69
- ### 3. Create a Service Account
70
-
71
- 1. Go to **IAM & Admin** → **Service Accounts**
72
- 2. Click **Create Service Account**
73
- 3. Enter name and description
74
- 4. Click **Create and Continue**
75
- 5. Skip the optional steps, click **Done**
76
-
77
- ### 4. Create Service Account Key
78
-
79
- > **Note**: If your organization has the `iam.disableServiceAccountKeyCreation` constraint enabled, you'll need to:
80
- > 1. Go to **IAM & Admin** → **Organization Policies**
81
- > 2. Find `iam.disableServiceAccountKeyCreation`
82
- > 3. Click **Manage Policy** → **Override parent's policy**
83
- > 4. Set to **Not enforced** (or add an exception for your project)
84
-
85
- 1. Click on your service account
86
- 2. Go to **Keys** tab
87
- 3. Click **Add Key** → **Create new key**
88
- 4. Select **JSON** and click **Create**
89
- 5. Save the downloaded file
90
- 6. Copy the entire JSON content → `GOOGLE_CHAT_CREDENTIALS` (as a single line)
91
-
92
- ### 5. Configure Google Chat App
93
-
94
- 1. Go to [console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat)
95
- 2. Click **Configuration**
96
- 3. Fill in:
97
- - **App name**: Your bot's display name
98
- - **Avatar URL**: URL to your bot's avatar image
99
- - **Description**: What your bot does
100
- - **Interactive features**:
101
- - Enable **Receive 1:1 messages**
102
- - Enable **Join spaces and group conversations**
103
- - **Connection settings**: Select **App URL**
104
- - **App URL**: `https://your-domain.com/api/webhooks/gchat`
105
- - **Visibility**: Choose who can discover and install your app
106
- 4. Click **Save**
107
-
108
- **Important for button clicks**: The same App URL receives both message events and interactive events (card button clicks). Google Chat sends CARD_CLICKED events to this URL when users click buttons in cards.
109
-
110
- ### 6. Add Bot to a Space
111
-
112
- 1. Open Google Chat
113
- 2. Create or open a Space
114
- 3. Click the space name → **Manage apps & integrations** (or **Apps & integrations**)
115
- 4. Click **Add apps**
116
- 5. Search for your app name
117
- 6. Click **Add**
118
-
119
- ## (Optional) Pub/Sub for All Messages
120
-
121
- By default, Google Chat only sends webhooks for @mentions. To receive ALL messages in a space (for conversation context), you need to set up Workspace Events with Pub/Sub.
122
-
123
- ### 1. Create Pub/Sub Topic
124
-
125
- 1. Go to **Pub/Sub** → **Topics**
126
- 2. Click **Create Topic**
127
- 3. Enter topic ID (e.g., `chat-events`)
128
- 4. Uncheck **Add a default subscription**
129
- 5. Click **Create**
130
- 6. Copy the full topic name → `GOOGLE_CHAT_PUBSUB_TOPIC`
131
- - Format: `projects/your-project-id/topics/chat-events`
132
-
133
- ### 2. Grant Chat Service Account Access
134
-
135
- > **Note**: If your organization has the `iam.allowedPolicyMemberDomains` constraint, you may need to temporarily relax it or use the console workaround below.
136
-
137
- 1. Go to your Pub/Sub topic
138
- 2. Click **Permissions** tab (or **Show Info Panel** → **Permissions**)
139
- 3. Click **Add Principal**
140
- 4. Enter: `chat-api-push@system.gserviceaccount.com`
141
- 5. Select role: **Pub/Sub Publisher**
142
- 6. Click **Save**
143
-
144
- **If you get a policy error**, try via Cloud Console:
145
- 1. Go to **Pub/Sub** → **Topics**
146
- 2. Check the box next to your topic
147
- 3. Click **Permissions** in the info panel
148
- 4. Click **Add Principal**
149
- 5. Add `chat-api-push@system.gserviceaccount.com` with **Pub/Sub Publisher** role
150
-
151
- ### 3. Create Push Subscription
152
-
153
- 1. Go to **Pub/Sub** → **Subscriptions**
154
- 2. Click **Create Subscription**
155
- 3. Enter subscription ID (e.g., `chat-messages-push`)
156
- 4. Select your topic
157
- 5. **Delivery type**: Push
158
- 6. **Endpoint URL**: `https://your-domain.com/api/webhooks/gchat`
159
- 7. Click **Create**
160
-
161
- ### 4. Enable Domain-Wide Delegation
162
-
163
- To create Workspace Events subscriptions and initiate DMs, you need domain-wide delegation:
164
-
165
- **Step 1: Enable delegation on the Service Account (GCP Console)**
166
-
167
- 1. Go to [IAM & Admin → Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts)
168
- 2. Click on your service account
169
- 3. Go to **Details** tab
170
- 4. Check **Enable Google Workspace Domain-wide Delegation**
171
- 5. Click **Save**
172
- 6. Go to **Advanced settings** (or click on the service account again)
173
- 7. Copy the **Client ID** - this is a **numeric ID** (e.g., `123456789012345678901`), NOT the email address
174
-
175
- **Step 2: Authorize the Client ID (Google Admin Console)**
176
-
177
- 1. Go to [Google Admin Console](https://admin.google.com)
178
- 2. Go to **Security** → **Access and data control** → **API controls**
179
- 3. Click **Manage Domain Wide Delegation**
180
- 4. Click **Add new**
181
- 5. Enter:
182
- - **Client ID**: The numeric ID from Step 1 (e.g., `123456789012345678901`)
183
- - **OAuth Scopes** (all on one line, comma-separated):
184
- ```
185
- https://www.googleapis.com/auth/chat.spaces.readonly,https://www.googleapis.com/auth/chat.messages.readonly,https://www.googleapis.com/auth/chat.spaces,https://www.googleapis.com/auth/chat.spaces.create
186
- ```
187
- 6. Click **Authorize**
188
-
189
- **Step 3: Set environment variable**
190
-
191
- Set `GOOGLE_CHAT_IMPERSONATE_USER` to an admin user email in your domain (e.g., `admin@yourdomain.com`). This user will be impersonated when creating DM spaces and Workspace Events subscriptions.
192
-
193
- ## Features
194
-
195
- - Message posting and editing
196
- - Thread subscriptions
197
- - Reaction events (via Workspace Events)
198
- - File attachments
199
- - Rich cards (Google Chat Cards)
200
- - Action callbacks (card buttons)
201
- - Direct messages
202
- - Space management
203
-
204
- ## Limitations
205
-
206
- - **Typing indicators**: Not supported by Google Chat API
207
- - **Adding reactions**: Requires domain-wide delegation (appears from impersonated user, not bot)
208
-
209
- ## Troubleshooting
210
-
211
- ### No webhook received
212
- - Verify the App URL is correct in Google Chat configuration
213
- - Check that the Chat API is enabled
214
- - Ensure the service account has the necessary permissions
215
-
216
- ### Pub/Sub not working
217
- - Verify `chat-api-push@system.gserviceaccount.com` has Pub/Sub Publisher role
218
- - Check that the push subscription URL is correct
219
- - Verify domain-wide delegation is configured with correct scopes
220
- - Check `GOOGLE_CHAT_IMPERSONATE_USER` is a valid admin email
221
-
222
- ### "Permission denied" for Workspace Events
223
- - Ensure domain-wide delegation is configured
224
- - Verify the OAuth scopes are exactly as specified
225
- - Check that the impersonated user has access to the spaces
226
-
227
- ### "Insufficient Permission" for DMs (openDM)
228
- - DMs require domain-wide delegation with `chat.spaces` and `chat.spaces.create` scopes
229
- - Add these scopes to your domain-wide delegation configuration in Google Admin Console
230
- - Set `GOOGLE_CHAT_IMPERSONATE_USER` to an admin email in your domain
231
- - Scope changes can take up to 24 hours to propagate
232
-
233
- ### "unauthorized_client" error
234
- - The Client ID is not registered in Google Admin Console
235
- - Or domain-wide delegation is not enabled on the service account
34
+ ## Documentation
236
35
 
237
- ### Button clicks (CARD_CLICKED) not received
238
- - Verify "Interactive features" is enabled in the Google Chat app configuration
239
- - Check that the App URL is correctly set and accessible
240
- - Button clicks go to the same webhook URL as messages
241
- - Ensure your button elements have valid `id` attributes (these become the `actionId`)
36
+ Full setup instructions, configuration reference, and features at [chat-sdk.dev/docs/adapters/gchat](https://chat-sdk.dev/docs/adapters/gchat).
242
37
 
243
38
  ## License
244
39
 
package/dist/index.d.ts CHANGED
@@ -6,10 +6,10 @@ import { google } from 'googleapis';
6
6
  */
7
7
  /** Google Chat-specific thread ID data */
8
8
  interface GoogleChatThreadId {
9
- spaceName: string;
10
- threadName?: string;
11
9
  /** Whether this is a DM space */
12
10
  isDM?: boolean;
11
+ spaceName: string;
12
+ threadName?: string;
13
13
  }
14
14
 
15
15
  /**
@@ -20,30 +20,26 @@ interface GoogleChatThreadId {
20
20
  */
21
21
 
22
22
  interface GoogleChatCard {
23
- cardId?: string;
24
23
  card: {
25
24
  header?: GoogleChatCardHeader;
26
25
  sections: GoogleChatCardSection[];
27
26
  };
27
+ cardId?: string;
28
28
  }
29
29
  interface GoogleChatCardHeader {
30
- title: string;
31
- subtitle?: string;
32
- imageUrl?: string;
33
30
  imageType?: "CIRCLE" | "SQUARE";
31
+ imageUrl?: string;
32
+ subtitle?: string;
33
+ title: string;
34
34
  }
35
35
  interface GoogleChatCardSection {
36
+ collapsible?: boolean;
36
37
  header?: string;
37
38
  widgets: GoogleChatWidget[];
38
- collapsible?: boolean;
39
39
  }
40
40
  interface GoogleChatWidget {
41
- textParagraph?: {
42
- text: string;
43
- };
44
- image?: {
45
- imageUrl: string;
46
- altText?: string;
41
+ buttonList?: {
42
+ buttons: (GoogleChatButton | GoogleChatLinkButton)[];
47
43
  };
48
44
  decoratedText?: {
49
45
  topLabel?: string;
@@ -53,13 +49,21 @@ interface GoogleChatWidget {
53
49
  knownIcon?: string;
54
50
  };
55
51
  };
56
- buttonList?: {
57
- buttons: (GoogleChatButton | GoogleChatLinkButton)[];
58
- };
59
52
  divider?: Record<string, never>;
53
+ image?: {
54
+ imageUrl: string;
55
+ altText?: string;
56
+ };
57
+ textParagraph?: {
58
+ text: string;
59
+ };
60
60
  }
61
61
  interface GoogleChatButton {
62
- text: string;
62
+ color?: {
63
+ red: number;
64
+ green: number;
65
+ blue: number;
66
+ };
63
67
  onClick: {
64
68
  action: {
65
69
  function: string;
@@ -69,24 +73,20 @@ interface GoogleChatButton {
69
73
  }>;
70
74
  };
71
75
  };
76
+ text: string;
77
+ }
78
+ interface GoogleChatLinkButton {
72
79
  color?: {
73
80
  red: number;
74
81
  green: number;
75
82
  blue: number;
76
83
  };
77
- }
78
- interface GoogleChatLinkButton {
79
- text: string;
80
84
  onClick: {
81
85
  openLink: {
82
86
  url: string;
83
87
  };
84
88
  };
85
- color?: {
86
- red: number;
87
- green: number;
88
- blue: number;
89
- };
89
+ text: string;
90
90
  }
91
91
  /**
92
92
  * Options for card conversion.
@@ -152,19 +152,19 @@ declare class GoogleChatFormatConverter extends BaseFormatConverter {
152
152
 
153
153
  /** Options for creating a space subscription */
154
154
  interface CreateSpaceSubscriptionOptions {
155
- /** The space name (e.g., "spaces/AAAA...") */
156
- spaceName: string;
157
155
  /** The Pub/Sub topic to receive events (e.g., "projects/my-project/topics/my-topic") */
158
156
  pubsubTopic: string;
157
+ /** The space name (e.g., "spaces/AAAA...") */
158
+ spaceName: string;
159
159
  /** Optional TTL for the subscription in seconds (default: 1 day, max: 1 day for Chat) */
160
160
  ttlSeconds?: number;
161
161
  }
162
162
  /** Result of creating a space subscription */
163
163
  interface SpaceSubscriptionResult {
164
- /** The subscription resource name */
165
- name: string;
166
164
  /** When the subscription expires (ISO 8601) */
167
165
  expireTime: string;
166
+ /** The subscription resource name */
167
+ name: string;
168
168
  }
169
169
  /** Pub/Sub push message wrapper (what Google sends to your endpoint) */
170
170
  interface PubSubPushMessage {
@@ -179,6 +179,10 @@ interface PubSubPushMessage {
179
179
  }
180
180
  /** Google Chat reaction data */
181
181
  interface GoogleChatReaction {
182
+ /** The emoji */
183
+ emoji?: {
184
+ unicode?: string;
185
+ };
182
186
  /** Reaction resource name */
183
187
  name: string;
184
188
  /** The user who added/removed the reaction */
@@ -187,30 +191,26 @@ interface GoogleChatReaction {
187
191
  displayName?: string;
188
192
  type?: string;
189
193
  };
190
- /** The emoji */
191
- emoji?: {
192
- unicode?: string;
193
- };
194
194
  }
195
195
  /** Decoded Workspace Events notification payload */
196
196
  interface WorkspaceEventNotification {
197
- /** The subscription that triggered this event */
198
- subscription: string;
199
- /** The resource being watched (e.g., "//chat.googleapis.com/spaces/AAAA") */
200
- targetResource: string;
201
- /** Event type (e.g., "google.workspace.chat.message.v1.created") */
202
- eventType: string;
203
197
  /** When the event occurred */
204
198
  eventTime: string;
199
+ /** Event type (e.g., "google.workspace.chat.message.v1.created") */
200
+ eventType: string;
201
+ /** Present for message.created events */
202
+ message?: GoogleChatMessage;
203
+ /** Present for reaction.created/deleted events */
204
+ reaction?: GoogleChatReaction;
205
205
  /** Space info */
206
206
  space?: {
207
207
  name: string;
208
208
  type: string;
209
209
  };
210
- /** Present for message.created events */
211
- message?: GoogleChatMessage;
212
- /** Present for reaction.created/deleted events */
213
- reaction?: GoogleChatReaction;
210
+ /** The subscription that triggered this event */
211
+ subscription: string;
212
+ /** The resource being watched (e.g., "//chat.googleapis.com/spaces/AAAA") */
213
+ targetResource: string;
214
214
  }
215
215
  /** Service account credentials for authentication */
216
216
  interface ServiceAccountCredentials$1 {
@@ -297,38 +297,40 @@ interface ServiceAccountCredentials {
297
297
  }
298
298
  /** Base config options shared by all auth methods */
299
299
  interface GoogleChatAdapterBaseConfig {
300
- /** Logger instance for error reporting */
301
- logger: Logger;
302
- /** Override bot username (optional) */
303
- userName?: string;
304
300
  /**
305
- * Pub/Sub topic for receiving all messages via Workspace Events.
306
- * When set, the adapter will automatically create subscriptions when added to a space.
307
- * Format: "projects/my-project/topics/my-topic"
301
+ * HTTP endpoint URL for button click actions.
302
+ * Required for HTTP endpoint apps - button clicks will be routed to this URL.
303
+ * Should be the full URL of your webhook endpoint (e.g., "https://your-app.vercel.app/api/webhooks/gchat")
308
304
  */
309
- pubsubTopic?: string;
305
+ endpointUrl?: string;
310
306
  /**
311
307
  * User email to impersonate for Workspace Events API calls.
312
308
  * Required when using domain-wide delegation.
313
309
  * This user must have access to the Chat spaces you want to subscribe to.
314
310
  */
315
311
  impersonateUser?: string;
312
+ /** Logger instance for error reporting */
313
+ logger: Logger;
316
314
  /**
317
- * HTTP endpoint URL for button click actions.
318
- * Required for HTTP endpoint apps - button clicks will be routed to this URL.
319
- * Should be the full URL of your webhook endpoint (e.g., "https://your-app.vercel.app/api/webhooks/gchat")
315
+ * Pub/Sub topic for receiving all messages via Workspace Events.
316
+ * When set, the adapter will automatically create subscriptions when added to a space.
317
+ * Format: "projects/my-project/topics/my-topic"
320
318
  */
321
- endpointUrl?: string;
319
+ pubsubTopic?: string;
320
+ /** Override bot username (optional) */
321
+ userName?: string;
322
322
  }
323
323
  /** Config using service account credentials (JSON key file) */
324
324
  interface GoogleChatAdapterServiceAccountConfig extends GoogleChatAdapterBaseConfig {
325
+ auth?: never;
325
326
  /** Service account credentials JSON */
326
327
  credentials: ServiceAccountCredentials;
327
- auth?: never;
328
328
  useApplicationDefaultCredentials?: never;
329
329
  }
330
330
  /** Config using Application Default Credentials (ADC) or Workload Identity Federation */
331
331
  interface GoogleChatAdapterADCConfig extends GoogleChatAdapterBaseConfig {
332
+ auth?: never;
333
+ credentials?: never;
332
334
  /**
333
335
  * Use Application Default Credentials.
334
336
  * Works with:
@@ -338,8 +340,6 @@ interface GoogleChatAdapterADCConfig extends GoogleChatAdapterBaseConfig {
338
340
  * - gcloud auth application-default login (local development)
339
341
  */
340
342
  useApplicationDefaultCredentials: true;
341
- credentials?: never;
342
- auth?: never;
343
343
  }
344
344
  /** Config using a custom auth client */
345
345
  interface GoogleChatAdapterCustomAuthConfig extends GoogleChatAdapterBaseConfig {
@@ -352,25 +352,6 @@ type GoogleChatAdapterConfig = GoogleChatAdapterServiceAccountConfig | GoogleCha
352
352
 
353
353
  /** Google Chat message structure */
354
354
  interface GoogleChatMessage {
355
- name: string;
356
- sender: {
357
- name: string;
358
- displayName: string;
359
- type: string;
360
- email?: string;
361
- };
362
- text: string;
363
- argumentText?: string;
364
- formattedText?: string;
365
- thread?: {
366
- name: string;
367
- };
368
- space?: {
369
- name: string;
370
- type: string;
371
- displayName?: string;
372
- };
373
- createTime: string;
374
355
  annotations?: Array<{
375
356
  type: string;
376
357
  startIndex?: number;
@@ -384,45 +365,55 @@ interface GoogleChatMessage {
384
365
  type: string;
385
366
  };
386
367
  }>;
368
+ argumentText?: string;
387
369
  attachment?: Array<{
388
370
  name: string;
389
371
  contentName: string;
390
372
  contentType: string;
391
373
  downloadUri?: string;
392
374
  }>;
375
+ createTime: string;
376
+ formattedText?: string;
377
+ name: string;
378
+ sender: {
379
+ name: string;
380
+ displayName: string;
381
+ type: string;
382
+ email?: string;
383
+ };
384
+ space?: {
385
+ name: string;
386
+ type: string;
387
+ displayName?: string;
388
+ };
389
+ text: string;
390
+ thread?: {
391
+ name: string;
392
+ };
393
393
  }
394
394
  /** Google Chat space structure */
395
395
  interface GoogleChatSpace {
396
- name: string;
397
- type: string;
398
396
  displayName?: string;
397
+ name: string;
398
+ /** Whether this is a single-user DM with the bot */
399
+ singleUserBotDm?: boolean;
399
400
  spaceThreadingState?: string;
400
401
  /** Space type in newer API format: "SPACE", "GROUP_CHAT", "DIRECT_MESSAGE" */
401
402
  spaceType?: string;
402
- /** Whether this is a single-user DM with the bot */
403
- singleUserBotDm?: boolean;
403
+ type: string;
404
404
  }
405
405
  /** Google Chat user structure */
406
406
  interface GoogleChatUser {
407
- name: string;
408
407
  displayName: string;
409
- type: string;
410
408
  email?: string;
409
+ name: string;
410
+ type: string;
411
411
  }
412
412
  /**
413
413
  * Google Workspace Add-ons event format.
414
414
  * This is the format used when configuring the app via Google Cloud Console.
415
415
  */
416
416
  interface GoogleChatEvent {
417
- commonEventObject?: {
418
- userLocale?: string;
419
- hostApp?: string;
420
- platform?: string;
421
- /** The function name invoked (for card clicks) */
422
- invokedFunction?: string;
423
- /** Parameters passed to the function */
424
- parameters?: Record<string, string>;
425
- };
426
417
  chat?: {
427
418
  user?: GoogleChatUser;
428
419
  eventTime?: string;
@@ -445,30 +436,39 @@ interface GoogleChatEvent {
445
436
  user: GoogleChatUser;
446
437
  };
447
438
  };
439
+ commonEventObject?: {
440
+ userLocale?: string;
441
+ hostApp?: string;
442
+ platform?: string;
443
+ /** The function name invoked (for card clicks) */
444
+ invokedFunction?: string;
445
+ /** Parameters passed to the function */
446
+ parameters?: Record<string, string>;
447
+ };
448
448
  }
449
449
  declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown> {
450
450
  readonly name = "gchat";
451
451
  readonly userName: string;
452
452
  /** Bot's user ID (e.g., "users/123...") - learned from annotations */
453
453
  botUserId?: string;
454
- private chatApi;
454
+ private readonly chatApi;
455
455
  private chat;
456
456
  private state;
457
- private logger;
458
- private formatConverter;
459
- private pubsubTopic?;
460
- private credentials?;
461
- private useADC;
457
+ private readonly logger;
458
+ private readonly formatConverter;
459
+ private readonly pubsubTopic?;
460
+ private readonly credentials?;
461
+ private readonly useADC;
462
462
  /** Custom auth client (e.g., Vercel OIDC) */
463
- private customAuth?;
463
+ private readonly customAuth?;
464
464
  /** Auth client for making authenticated requests */
465
- private authClient;
465
+ private readonly authClient;
466
466
  /** User email to impersonate for Workspace Events API (domain-wide delegation) */
467
- private impersonateUser?;
467
+ private readonly impersonateUser?;
468
468
  /** In-progress subscription creations to prevent duplicate requests */
469
- private pendingSubscriptions;
469
+ private readonly pendingSubscriptions;
470
470
  /** Chat API client with impersonation for user-context operations (DMs, etc.) */
471
- private impersonatedChatApi?;
471
+ private readonly impersonatedChatApi?;
472
472
  /** HTTP endpoint URL for button click actions */
473
473
  private endpointUrl?;
474
474
  /** User info cache for display name lookups - initialized later in initialize() */