@chat-adapter/gchat 4.0.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.
@@ -0,0 +1,567 @@
1
+ import { CardElement, BaseFormatConverter, Root, Adapter, ChatInstance, WebhookOptions, PostableMessage, RawMessage, EmojiValue, FetchOptions, Message, ThreadInfo, FormattedContent } from 'chat';
2
+ import { google } from 'googleapis';
3
+
4
+ /**
5
+ * Google Chat Card converter for cross-platform cards.
6
+ *
7
+ * Converts CardElement to Google Chat Card v2 format.
8
+ * @see https://developers.google.com/chat/api/reference/rest/v1/cards
9
+ */
10
+
11
+ interface GoogleChatCard {
12
+ cardId?: string;
13
+ card: {
14
+ header?: GoogleChatCardHeader;
15
+ sections: GoogleChatCardSection[];
16
+ };
17
+ }
18
+ interface GoogleChatCardHeader {
19
+ title: string;
20
+ subtitle?: string;
21
+ imageUrl?: string;
22
+ imageType?: "CIRCLE" | "SQUARE";
23
+ }
24
+ interface GoogleChatCardSection {
25
+ header?: string;
26
+ widgets: GoogleChatWidget[];
27
+ collapsible?: boolean;
28
+ }
29
+ interface GoogleChatWidget {
30
+ textParagraph?: {
31
+ text: string;
32
+ };
33
+ image?: {
34
+ imageUrl: string;
35
+ altText?: string;
36
+ };
37
+ decoratedText?: {
38
+ topLabel?: string;
39
+ text: string;
40
+ bottomLabel?: string;
41
+ startIcon?: {
42
+ knownIcon?: string;
43
+ };
44
+ };
45
+ buttonList?: {
46
+ buttons: GoogleChatButton[];
47
+ };
48
+ divider?: Record<string, never>;
49
+ }
50
+ interface GoogleChatButton {
51
+ text: string;
52
+ onClick: {
53
+ action: {
54
+ function: string;
55
+ parameters: Array<{
56
+ key: string;
57
+ value: string;
58
+ }>;
59
+ };
60
+ };
61
+ color?: {
62
+ red: number;
63
+ green: number;
64
+ blue: number;
65
+ };
66
+ }
67
+ /**
68
+ * Convert a CardElement to Google Chat Card v2 format.
69
+ */
70
+ declare function cardToGoogleCard(card: CardElement, cardId?: string): GoogleChatCard;
71
+ /**
72
+ * Generate fallback text from a card element.
73
+ * Used when cards aren't supported.
74
+ */
75
+ declare function cardToFallbackText(card: CardElement): string;
76
+
77
+ /**
78
+ * Google Chat-specific format conversion using AST-based parsing.
79
+ *
80
+ * Google Chat supports a subset of text formatting:
81
+ * - Bold: *text*
82
+ * - Italic: _text_
83
+ * - Strikethrough: ~text~
84
+ * - Monospace: `text`
85
+ * - Code blocks: ```text```
86
+ * - Links are auto-detected
87
+ *
88
+ * Very similar to Slack's mrkdwn format.
89
+ */
90
+
91
+ declare class GoogleChatFormatConverter extends BaseFormatConverter {
92
+ /**
93
+ * Render an AST to Google Chat format.
94
+ */
95
+ fromAst(ast: Root): string;
96
+ /**
97
+ * Parse Google Chat message into an AST.
98
+ */
99
+ toAst(gchatText: string): Root;
100
+ private nodeToGChat;
101
+ }
102
+
103
+ /**
104
+ * Google Workspace Events API integration for receiving all messages in a space.
105
+ *
106
+ * By default, Google Chat only sends webhooks for @mentions. To receive ALL messages
107
+ * in a space, you need to create a Workspace Events subscription that publishes to
108
+ * a Pub/Sub topic, which then pushes to your webhook endpoint.
109
+ *
110
+ * Setup flow:
111
+ * 1. Create a Pub/Sub topic in your GCP project
112
+ * 2. Create a Pub/Sub push subscription pointing to your /api/webhooks/gchat/pubsub endpoint
113
+ * 3. Call createSpaceSubscription() to subscribe to message events for a space
114
+ * 4. Handle Pub/Sub messages in your webhook with handlePubSubMessage()
115
+ */
116
+
117
+ /** Options for creating a space subscription */
118
+ interface CreateSpaceSubscriptionOptions {
119
+ /** The space name (e.g., "spaces/AAAA...") */
120
+ spaceName: string;
121
+ /** The Pub/Sub topic to receive events (e.g., "projects/my-project/topics/my-topic") */
122
+ pubsubTopic: string;
123
+ /** Optional TTL for the subscription in seconds (default: 1 day, max: 1 day for Chat) */
124
+ ttlSeconds?: number;
125
+ }
126
+ /** Result of creating a space subscription */
127
+ interface SpaceSubscriptionResult {
128
+ /** The subscription resource name */
129
+ name: string;
130
+ /** When the subscription expires (ISO 8601) */
131
+ expireTime: string;
132
+ }
133
+ /** Pub/Sub push message wrapper (what Google sends to your endpoint) */
134
+ interface PubSubPushMessage {
135
+ message: {
136
+ /** Base64 encoded event data */
137
+ data: string;
138
+ messageId: string;
139
+ publishTime: string;
140
+ attributes?: Record<string, string>;
141
+ };
142
+ subscription: string;
143
+ }
144
+ /** Google Chat reaction data */
145
+ interface GoogleChatReaction {
146
+ /** Reaction resource name */
147
+ name: string;
148
+ /** The user who added/removed the reaction */
149
+ user?: {
150
+ name: string;
151
+ displayName?: string;
152
+ type?: string;
153
+ };
154
+ /** The emoji */
155
+ emoji?: {
156
+ unicode?: string;
157
+ };
158
+ }
159
+ /** Decoded Workspace Events notification payload */
160
+ interface WorkspaceEventNotification {
161
+ /** The subscription that triggered this event */
162
+ subscription: string;
163
+ /** The resource being watched (e.g., "//chat.googleapis.com/spaces/AAAA") */
164
+ targetResource: string;
165
+ /** Event type (e.g., "google.workspace.chat.message.v1.created") */
166
+ eventType: string;
167
+ /** When the event occurred */
168
+ eventTime: string;
169
+ /** Space info */
170
+ space?: {
171
+ name: string;
172
+ type: string;
173
+ };
174
+ /** Present for message.created events */
175
+ message?: GoogleChatMessage;
176
+ /** Present for reaction.created/deleted events */
177
+ reaction?: GoogleChatReaction;
178
+ }
179
+ /** Service account credentials for authentication */
180
+ interface ServiceAccountCredentials$1 {
181
+ client_email: string;
182
+ private_key: string;
183
+ project_id?: string;
184
+ }
185
+ /** Auth options - service account, ADC, or custom auth client */
186
+ type WorkspaceEventsAuthOptions = {
187
+ credentials: ServiceAccountCredentials$1;
188
+ impersonateUser?: string;
189
+ } | {
190
+ useApplicationDefaultCredentials: true;
191
+ impersonateUser?: string;
192
+ } | {
193
+ auth: Parameters<typeof google.workspaceevents>[0]["auth"];
194
+ };
195
+ /**
196
+ * Create a Workspace Events subscription to receive all messages in a Chat space.
197
+ *
198
+ * Prerequisites:
199
+ * - Enable the "Google Workspace Events API" in your GCP project
200
+ * - Create a Pub/Sub topic and grant the Chat service account publish permissions
201
+ * - The calling user/service account needs permission to access the space
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const result = await createSpaceSubscription({
206
+ * spaceName: "spaces/AAAAxxxxxx",
207
+ * pubsubTopic: "projects/my-project/topics/chat-events",
208
+ * }, {
209
+ * credentials: {
210
+ * client_email: "...",
211
+ * private_key: "...",
212
+ * }
213
+ * });
214
+ * ```
215
+ */
216
+ declare function createSpaceSubscription(options: CreateSpaceSubscriptionOptions, auth: WorkspaceEventsAuthOptions): Promise<SpaceSubscriptionResult>;
217
+ /**
218
+ * List active subscriptions for a target resource.
219
+ */
220
+ declare function listSpaceSubscriptions(spaceName: string, auth: WorkspaceEventsAuthOptions): Promise<Array<{
221
+ name: string;
222
+ expireTime: string;
223
+ eventTypes: string[];
224
+ }>>;
225
+ /**
226
+ * Delete a Workspace Events subscription.
227
+ */
228
+ declare function deleteSpaceSubscription(subscriptionName: string, auth: WorkspaceEventsAuthOptions): Promise<void>;
229
+ /**
230
+ * Decode a Pub/Sub push message into a Workspace Event notification.
231
+ *
232
+ * The message uses CloudEvents format where event metadata is in attributes
233
+ * (ce-type, ce-source, ce-subject, ce-time) and the payload is base64 encoded.
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * // In your /api/webhooks/gchat/pubsub route:
238
+ * const body = await request.json();
239
+ * const event = decodePubSubMessage(body);
240
+ *
241
+ * if (event.eventType === "google.workspace.chat.message.v1.created") {
242
+ * // Handle new message
243
+ * console.log("New message:", event.message?.text);
244
+ * }
245
+ * ```
246
+ */
247
+ declare function decodePubSubMessage(pushMessage: PubSubPushMessage): WorkspaceEventNotification;
248
+ /**
249
+ * Verify a Pub/Sub push message is authentic.
250
+ * In production, you should verify the JWT token in the Authorization header.
251
+ *
252
+ * @see https://cloud.google.com/pubsub/docs/authenticate-push-subscriptions
253
+ */
254
+ declare function verifyPubSubRequest(request: Request, _expectedAudience?: string): boolean;
255
+
256
+ /** Service account credentials for JWT auth */
257
+ interface ServiceAccountCredentials {
258
+ client_email: string;
259
+ private_key: string;
260
+ project_id?: string;
261
+ }
262
+ /** Base config options shared by all auth methods */
263
+ interface GoogleChatAdapterBaseConfig {
264
+ /** Override bot username (optional) */
265
+ userName?: string;
266
+ /**
267
+ * Pub/Sub topic for receiving all messages via Workspace Events.
268
+ * When set, the adapter will automatically create subscriptions when added to a space.
269
+ * Format: "projects/my-project/topics/my-topic"
270
+ */
271
+ pubsubTopic?: string;
272
+ /**
273
+ * User email to impersonate for Workspace Events API calls.
274
+ * Required when using domain-wide delegation.
275
+ * This user must have access to the Chat spaces you want to subscribe to.
276
+ */
277
+ impersonateUser?: string;
278
+ }
279
+ /** Config using service account credentials (JSON key file) */
280
+ interface GoogleChatAdapterServiceAccountConfig extends GoogleChatAdapterBaseConfig {
281
+ /** Service account credentials JSON */
282
+ credentials: ServiceAccountCredentials;
283
+ auth?: never;
284
+ useApplicationDefaultCredentials?: never;
285
+ }
286
+ /** Config using Application Default Credentials (ADC) or Workload Identity Federation */
287
+ interface GoogleChatAdapterADCConfig extends GoogleChatAdapterBaseConfig {
288
+ /**
289
+ * Use Application Default Credentials.
290
+ * Works with:
291
+ * - GOOGLE_APPLICATION_CREDENTIALS env var pointing to a JSON key file
292
+ * - Workload Identity Federation (external_account JSON)
293
+ * - GCE/Cloud Run/Cloud Functions default service account
294
+ * - gcloud auth application-default login (local development)
295
+ */
296
+ useApplicationDefaultCredentials: true;
297
+ credentials?: never;
298
+ auth?: never;
299
+ }
300
+ /** Config using a custom auth client */
301
+ interface GoogleChatAdapterCustomAuthConfig extends GoogleChatAdapterBaseConfig {
302
+ /** Custom auth client (JWT, OAuth2, GoogleAuth, etc.) */
303
+ auth: Parameters<typeof google.chat>[0]["auth"];
304
+ credentials?: never;
305
+ useApplicationDefaultCredentials?: never;
306
+ }
307
+ type GoogleChatAdapterConfig = GoogleChatAdapterServiceAccountConfig | GoogleChatAdapterADCConfig | GoogleChatAdapterCustomAuthConfig;
308
+ /** Google Chat-specific thread ID data */
309
+ interface GoogleChatThreadId {
310
+ spaceName: string;
311
+ threadName?: string;
312
+ /** Whether this is a DM space */
313
+ isDM?: boolean;
314
+ }
315
+ /** Google Chat message structure */
316
+ interface GoogleChatMessage {
317
+ name: string;
318
+ sender: {
319
+ name: string;
320
+ displayName: string;
321
+ type: string;
322
+ email?: string;
323
+ };
324
+ text: string;
325
+ argumentText?: string;
326
+ formattedText?: string;
327
+ thread?: {
328
+ name: string;
329
+ };
330
+ space?: {
331
+ name: string;
332
+ type: string;
333
+ displayName?: string;
334
+ };
335
+ createTime: string;
336
+ annotations?: Array<{
337
+ type: string;
338
+ startIndex?: number;
339
+ length?: number;
340
+ userMention?: {
341
+ user: {
342
+ name: string;
343
+ displayName?: string;
344
+ type: string;
345
+ };
346
+ type: string;
347
+ };
348
+ }>;
349
+ attachment?: Array<{
350
+ name: string;
351
+ contentName: string;
352
+ contentType: string;
353
+ downloadUri?: string;
354
+ }>;
355
+ }
356
+ /** Google Chat space structure */
357
+ interface GoogleChatSpace {
358
+ name: string;
359
+ type: string;
360
+ displayName?: string;
361
+ spaceThreadingState?: string;
362
+ /** Space type in newer API format: "SPACE", "GROUP_CHAT", "DIRECT_MESSAGE" */
363
+ spaceType?: string;
364
+ /** Whether this is a single-user DM with the bot */
365
+ singleUserBotDm?: boolean;
366
+ }
367
+ /** Google Chat user structure */
368
+ interface GoogleChatUser {
369
+ name: string;
370
+ displayName: string;
371
+ type: string;
372
+ email?: string;
373
+ }
374
+ /**
375
+ * Google Workspace Add-ons event format.
376
+ * This is the format used when configuring the app via Google Cloud Console.
377
+ */
378
+ interface GoogleChatEvent {
379
+ commonEventObject?: {
380
+ userLocale?: string;
381
+ hostApp?: string;
382
+ platform?: string;
383
+ /** The function name invoked (for card clicks) */
384
+ invokedFunction?: string;
385
+ /** Parameters passed to the function */
386
+ parameters?: Record<string, string>;
387
+ };
388
+ chat?: {
389
+ user?: GoogleChatUser;
390
+ eventTime?: string;
391
+ messagePayload?: {
392
+ space: GoogleChatSpace;
393
+ message: GoogleChatMessage;
394
+ };
395
+ /** Present when the bot is added to a space */
396
+ addedToSpacePayload?: {
397
+ space: GoogleChatSpace;
398
+ };
399
+ /** Present when the bot is removed from a space */
400
+ removedFromSpacePayload?: {
401
+ space: GoogleChatSpace;
402
+ };
403
+ /** Present when a card button is clicked */
404
+ buttonClickedPayload?: {
405
+ space: GoogleChatSpace;
406
+ message: GoogleChatMessage;
407
+ user: GoogleChatUser;
408
+ };
409
+ };
410
+ }
411
+ declare class GoogleChatAdapter implements Adapter<GoogleChatThreadId, unknown> {
412
+ readonly name = "gchat";
413
+ readonly userName: string;
414
+ /** Bot's user ID (e.g., "users/123...") - learned from annotations */
415
+ botUserId?: string;
416
+ private chatApi;
417
+ private chat;
418
+ private state;
419
+ private logger;
420
+ private formatConverter;
421
+ private pubsubTopic?;
422
+ private credentials?;
423
+ private useADC;
424
+ /** Custom auth client (e.g., Vercel OIDC) */
425
+ private customAuth?;
426
+ /** Auth client for making authenticated requests */
427
+ private authClient;
428
+ /** User email to impersonate for Workspace Events API (domain-wide delegation) */
429
+ private impersonateUser?;
430
+ /** In-progress subscription creations to prevent duplicate requests */
431
+ private pendingSubscriptions;
432
+ /** Chat API client with impersonation for user-context operations (DMs, etc.) */
433
+ private impersonatedChatApi?;
434
+ constructor(config: GoogleChatAdapterConfig);
435
+ initialize(chat: ChatInstance): Promise<void>;
436
+ /**
437
+ * Called when a thread is subscribed to.
438
+ * Ensures the space has a Workspace Events subscription so we receive all messages.
439
+ */
440
+ onThreadSubscribe(threadId: string): Promise<void>;
441
+ /**
442
+ * Ensure a Workspace Events subscription exists for a space.
443
+ * Creates one if it doesn't exist or is about to expire.
444
+ */
445
+ private ensureSpaceSubscription;
446
+ /**
447
+ * Create a Workspace Events subscription and cache the result.
448
+ */
449
+ private createSpaceSubscriptionWithCache;
450
+ /**
451
+ * Check if a subscription already exists for this space.
452
+ */
453
+ private findExistingSubscription;
454
+ /**
455
+ * Get auth options for Workspace Events API calls.
456
+ */
457
+ private getAuthOptions;
458
+ handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
459
+ /**
460
+ * Handle Pub/Sub push messages from Workspace Events subscriptions.
461
+ * These contain all messages in a space, not just @mentions.
462
+ */
463
+ private handlePubSubMessage;
464
+ /**
465
+ * Handle message events received via Pub/Sub (Workspace Events).
466
+ */
467
+ private handlePubSubMessageEvent;
468
+ /**
469
+ * Handle reaction events received via Pub/Sub (Workspace Events).
470
+ * Fetches the message to get thread context for proper reply threading.
471
+ */
472
+ private handlePubSubReactionEvent;
473
+ /**
474
+ * Parse a Pub/Sub message into the standard Message format.
475
+ * Resolves user display names from cache since Pub/Sub messages don't include them.
476
+ */
477
+ private parsePubSubMessage;
478
+ /**
479
+ * Handle bot being added to a space - create Workspace Events subscription.
480
+ */
481
+ private handleAddedToSpace;
482
+ /**
483
+ * Handle card button clicks.
484
+ * Google Chat sends action data via commonEventObject.invokedFunction and parameters.
485
+ */
486
+ private handleCardClick;
487
+ /**
488
+ * Handle direct webhook message events (Add-ons format).
489
+ */
490
+ private handleMessageEvent;
491
+ private parseGoogleChatMessage;
492
+ postMessage(threadId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
493
+ /**
494
+ * Extract card element from a PostableMessage if present.
495
+ */
496
+ private extractCard;
497
+ /**
498
+ * Extract files from a PostableMessage if present.
499
+ */
500
+ private extractFiles;
501
+ /**
502
+ * Create an Attachment object from a Google Chat attachment.
503
+ */
504
+ private createAttachment;
505
+ editMessage(threadId: string, messageId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
506
+ deleteMessage(_threadId: string, messageId: string): Promise<void>;
507
+ addReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
508
+ removeReaction(_threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
509
+ startTyping(_threadId: string): Promise<void>;
510
+ /**
511
+ * Open a direct message conversation with a user.
512
+ * Returns a thread ID that can be used to post messages.
513
+ *
514
+ * For Google Chat, this first tries to find an existing DM space with the user.
515
+ * If no DM exists, it creates one using spaces.setup.
516
+ *
517
+ * @param userId - The user's resource name (e.g., "users/123456")
518
+ */
519
+ openDM(userId: string): Promise<string>;
520
+ fetchMessages(threadId: string, options?: FetchOptions): Promise<Message<unknown>[]>;
521
+ fetchThread(threadId: string): Promise<ThreadInfo>;
522
+ encodeThreadId(platformData: GoogleChatThreadId): string;
523
+ /**
524
+ * Check if a thread is a direct message conversation.
525
+ * Checks for the :dm marker in the thread ID which is set when
526
+ * processing DM messages or opening DMs.
527
+ */
528
+ isDM(threadId: string): boolean;
529
+ decodeThreadId(threadId: string): GoogleChatThreadId;
530
+ parseMessage(raw: unknown): Message<unknown>;
531
+ renderFormatted(content: FormattedContent): string;
532
+ /**
533
+ * Normalize bot mentions in message text.
534
+ * Google Chat uses the bot's display name (e.g., "@Chat SDK Demo") but the
535
+ * Chat SDK expects "@{userName}" format. This method replaces bot mentions
536
+ * with the adapter's userName so mention detection works properly.
537
+ * Also learns the bot's user ID from annotations for isMe detection.
538
+ */
539
+ private normalizeBotMentions;
540
+ /**
541
+ * Check if a message is from this bot.
542
+ *
543
+ * Bot user ID is learned dynamically from message annotations when the bot
544
+ * is @mentioned. Until we learn the ID, we cannot reliably determine isMe.
545
+ *
546
+ * This is safer than the previous approach of assuming all BOT messages are
547
+ * from self, which would incorrectly filter messages from other bots in
548
+ * multi-bot spaces (especially via Pub/Sub).
549
+ */
550
+ private isMessageFromSelf;
551
+ /**
552
+ * Cache user info for later lookup (e.g., when processing Pub/Sub messages).
553
+ */
554
+ private cacheUserInfo;
555
+ /**
556
+ * Get cached user info.
557
+ */
558
+ private getCachedUserInfo;
559
+ /**
560
+ * Resolve user display name, using cache if available.
561
+ */
562
+ private resolveUserDisplayName;
563
+ private handleGoogleChatError;
564
+ }
565
+ declare function createGoogleChatAdapter(config: GoogleChatAdapterConfig): GoogleChatAdapter;
566
+
567
+ export { type CreateSpaceSubscriptionOptions, GoogleChatAdapter, type GoogleChatAdapterADCConfig, type GoogleChatAdapterBaseConfig, type GoogleChatAdapterConfig, type GoogleChatAdapterCustomAuthConfig, type GoogleChatAdapterServiceAccountConfig, type GoogleChatEvent, GoogleChatFormatConverter, type GoogleChatMessage, type GoogleChatSpace, type GoogleChatThreadId, type GoogleChatUser, type PubSubPushMessage, type ServiceAccountCredentials, type SpaceSubscriptionResult, type WorkspaceEventNotification, type WorkspaceEventsAuthOptions, cardToFallbackText, cardToGoogleCard, createGoogleChatAdapter, createSpaceSubscription, decodePubSubMessage, deleteSpaceSubscription, listSpaceSubscriptions, verifyPubSubRequest };