@elizaos/plugin-google-chat 2.0.0-beta.1 → 2.0.3-beta.3

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -136
  3. package/auto-enable.ts +1 -1
  4. package/package.json +22 -4
  5. package/registry-entry.json +127 -0
  6. package/dist/src/accounts.d.ts +0 -16
  7. package/dist/src/accounts.d.ts.map +0 -1
  8. package/dist/src/accounts.js +0 -163
  9. package/dist/src/accounts.js.map +0 -1
  10. package/dist/src/actions/index.d.ts +0 -1
  11. package/dist/src/actions/index.d.ts.map +0 -1
  12. package/dist/src/actions/index.js +0 -3
  13. package/dist/src/actions/index.js.map +0 -1
  14. package/dist/src/config.d.ts +0 -91
  15. package/dist/src/config.d.ts.map +0 -1
  16. package/dist/src/config.js +0 -8
  17. package/dist/src/config.js.map +0 -1
  18. package/dist/src/connector-account-provider.d.ts +0 -22
  19. package/dist/src/connector-account-provider.d.ts.map +0 -1
  20. package/dist/src/connector-account-provider.js +0 -89
  21. package/dist/src/connector-account-provider.js.map +0 -1
  22. package/dist/src/index.d.ts +0 -18
  23. package/dist/src/index.d.ts.map +0 -1
  24. package/dist/src/index.js +0 -66
  25. package/dist/src/index.js.map +0 -1
  26. package/dist/src/providers/index.d.ts +0 -1
  27. package/dist/src/providers/index.d.ts.map +0 -1
  28. package/dist/src/providers/index.js +0 -3
  29. package/dist/src/providers/index.js.map +0 -1
  30. package/dist/src/service.d.ts +0 -62
  31. package/dist/src/service.d.ts.map +0 -1
  32. package/dist/src/service.js +0 -724
  33. package/dist/src/service.js.map +0 -1
  34. package/dist/src/types.d.ts +0 -216
  35. package/dist/src/types.d.ts.map +0 -1
  36. package/dist/src/types.js +0 -144
  37. package/dist/src/types.js.map +0 -1
  38. package/dist/src/workflow-credential-provider.d.ts +0 -21
  39. package/dist/src/workflow-credential-provider.d.ts.map +0 -1
  40. package/dist/src/workflow-credential-provider.js +0 -57
  41. package/dist/src/workflow-credential-provider.js.map +0 -1
@@ -1,724 +0,0 @@
1
- /**
2
- * Google Chat service implementation for ElizaOS.
3
- */
4
- import { logger, Service, } from "@elizaos/core";
5
- import { GoogleAuth } from "google-auth-library";
6
- import { DEFAULT_GOOGLE_CHAT_ACCOUNT_ID, listGoogleChatAccountIds, normalizeGoogleChatAccountId, readGoogleChatAccountId, resolveDefaultGoogleChatAccountId, resolveGoogleChatAccountSettings, } from "./accounts.js";
7
- import { GOOGLE_CHAT_SERVICE_NAME, GoogleChatApiError, GoogleChatAuthenticationError, GoogleChatConfigurationError, GoogleChatEventTypes, getSpaceDisplayName, isDirectMessage, normalizeSpaceTarget, normalizeUserTarget, } from "./types.js";
8
- const CHAT_API_BASE = "https://chat.googleapis.com/v1";
9
- const CHAT_UPLOAD_BASE = "https://chat.googleapis.com/upload/v1";
10
- const CHAT_SCOPE = "https://www.googleapis.com/auth/chat.bot";
11
- function normalizeGoogleChatQuery(query) {
12
- return query.trim().toLowerCase();
13
- }
14
- function scoreGoogleChatSpace(space, query) {
15
- const normalized = normalizeGoogleChatQuery(query);
16
- if (!normalized) {
17
- return 0.45;
18
- }
19
- const candidates = [space.name, space.displayName, space.spaceType, space.type]
20
- .filter((value) => typeof value === "string" && value.length > 0)
21
- .map((value) => value.toLowerCase());
22
- if (candidates.some((candidate) => candidate === normalized)) {
23
- return 1;
24
- }
25
- return candidates.some((candidate) => candidate.includes(normalized)) ? 0.8 : 0;
26
- }
27
- function googleChatSpaceToConnectorTarget(space, score = 0.55, accountId = DEFAULT_GOOGLE_CHAT_ACCOUNT_ID) {
28
- return {
29
- target: {
30
- source: GOOGLE_CHAT_SERVICE_NAME,
31
- accountId,
32
- channelId: space.name,
33
- },
34
- label: getSpaceDisplayName(space),
35
- kind: isDirectMessage(space) ? "user" : "room",
36
- description: space.threaded ? "Threaded Google Chat space" : "Google Chat space",
37
- score,
38
- contexts: ["social", "connectors"],
39
- metadata: {
40
- accountId,
41
- spaceType: space.type,
42
- singleUserBotDm: space.singleUserBotDm,
43
- threaded: space.threaded,
44
- },
45
- };
46
- }
47
- function normalizeConnectorLimit(limit, fallback = 50) {
48
- if (!Number.isFinite(limit) || !limit || limit <= 0) {
49
- return fallback;
50
- }
51
- return Math.min(Math.floor(limit), 200);
52
- }
53
- async function readStoredMessageMemories(runtime, roomId, limit) {
54
- return runtime.getMemories({
55
- tableName: "messages",
56
- roomId,
57
- limit,
58
- orderBy: "createdAt",
59
- orderDirection: "desc",
60
- });
61
- }
62
- async function readStoredMessagesForTargets(runtime, targets, limit) {
63
- const roomIds = Array.from(new Set(targets.map((target) => target.target.roomId).filter((id) => Boolean(id))));
64
- const chunks = await Promise.all(roomIds.map((roomId) => readStoredMessageMemories(runtime, roomId, limit)));
65
- return chunks
66
- .flat()
67
- .sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0))
68
- .slice(0, limit);
69
- }
70
- function filterMemoriesByQuery(memories, query, limit) {
71
- const normalized = query.trim().toLowerCase();
72
- if (!normalized) {
73
- return memories.slice(0, limit);
74
- }
75
- return memories
76
- .filter((memory) => {
77
- const text = typeof memory.content?.text === "string" ? memory.content.text : "";
78
- return text.toLowerCase().includes(normalized);
79
- })
80
- .slice(0, limit);
81
- }
82
- function getMutationMessageName(params) {
83
- const messageName = String(params.messageId ?? params.id ?? "").trim();
84
- if (!messageName) {
85
- throw new Error("Google Chat message operation requires messageId");
86
- }
87
- return messageName;
88
- }
89
- function googleChatOptionsFromContent(space, content, target) {
90
- const data = content.data;
91
- const googleChatData = (data?.googleChat && typeof data.googleChat === "object" ? data.googleChat : data);
92
- return {
93
- space,
94
- text: typeof content.text === "string" ? content.text : undefined,
95
- thread: target?.threadId ||
96
- (typeof googleChatData?.thread === "string" ? googleChatData.thread : undefined),
97
- attachments: Array.isArray(googleChatData?.attachments)
98
- ? googleChatData.attachments
99
- : undefined,
100
- };
101
- }
102
- export class GoogleChatService extends Service {
103
- static serviceType = GOOGLE_CHAT_SERVICE_NAME;
104
- capabilityDescription = "Google Chat service for sending and receiving messages in Google Workspace";
105
- states = new Map();
106
- defaultAccountId = DEFAULT_GOOGLE_CHAT_ACCOUNT_ID;
107
- static async start(runtime) {
108
- logger.info("Starting Google Chat service...");
109
- const service = new GoogleChatService(runtime);
110
- service.defaultAccountId = normalizeGoogleChatAccountId(resolveDefaultGoogleChatAccountId(runtime));
111
- for (const accountId of listGoogleChatAccountIds(runtime)) {
112
- const settings = service.loadSettings(accountId);
113
- if (settings.enabled === false) {
114
- continue;
115
- }
116
- service.validateSettings(settings);
117
- const state = {
118
- accountId: normalizeGoogleChatAccountId(settings.accountId),
119
- settings,
120
- auth: service.createAuth(settings),
121
- connected: false,
122
- cachedSpaces: [],
123
- };
124
- await service.testConnection(state);
125
- state.connected = true;
126
- service.states.set(state.accountId, state);
127
- GoogleChatService.registerSendHandlers(runtime, service, state.accountId);
128
- runtime.emitEvent(GoogleChatEventTypes.CONNECTION_READY, {
129
- runtime,
130
- service,
131
- accountId: state.accountId,
132
- });
133
- }
134
- if (service.states.size === 0) {
135
- const settings = service.loadSettings(service.defaultAccountId);
136
- service.validateSettings(settings);
137
- }
138
- logger.info("Google Chat service started successfully");
139
- return service;
140
- }
141
- static registerSendHandlers(runtime, service, accountId = service.getAccountId(runtime)) {
142
- accountId = normalizeGoogleChatAccountId(accountId);
143
- const sendHandler = async (handlerRuntime, target, content) => {
144
- await service.handleSendMessage(handlerRuntime, target, content);
145
- return undefined;
146
- };
147
- if (typeof runtime.registerMessageConnector === "function") {
148
- const registration = {
149
- source: GOOGLE_CHAT_SERVICE_NAME,
150
- accountId,
151
- label: "Google Chat",
152
- capabilities: [
153
- "send_message",
154
- "send_thread_reply",
155
- "send_attachment",
156
- "send_reaction",
157
- "list_spaces",
158
- "direct_message",
159
- ],
160
- supportedTargetKinds: ["room", "channel", "thread", "user"],
161
- contexts: ["social", "connectors"],
162
- description: "Send Google Chat messages to spaces, threaded conversations, and direct-message spaces.",
163
- metadata: {
164
- accountId,
165
- service: GOOGLE_CHAT_SERVICE_NAME,
166
- },
167
- sendHandler,
168
- resolveTargets: async (query) => {
169
- const directUser = normalizeUserTarget(query);
170
- const directTarget = directUser
171
- ? [
172
- {
173
- target: {
174
- source: GOOGLE_CHAT_SERVICE_NAME,
175
- accountId,
176
- channelId: directUser,
177
- },
178
- label: directUser,
179
- kind: "user",
180
- score: 0.95,
181
- contexts: ["social", "connectors"],
182
- metadata: { accountId },
183
- },
184
- ]
185
- : [];
186
- const spaces = await service.listConnectorSpaces(accountId);
187
- const spaceTargets = spaces
188
- .map((space) => ({ space, score: scoreGoogleChatSpace(space, query) }))
189
- .filter(({ score }) => score > 0)
190
- .map(({ space, score }) => googleChatSpaceToConnectorTarget(space, score, accountId));
191
- return [...directTarget, ...spaceTargets]
192
- .sort((left, right) => (right.score ?? 0) - (left.score ?? 0))
193
- .slice(0, 10);
194
- },
195
- listRecentTargets: async () => (await service.listConnectorSpaces(accountId))
196
- .slice(0, 10)
197
- .map((space) => googleChatSpaceToConnectorTarget(space, 0.55, accountId)),
198
- listRooms: async () => (await service.listConnectorSpaces(accountId)).map((space) => googleChatSpaceToConnectorTarget(space, 0.55, accountId)),
199
- fetchMessages: async (context, params) => {
200
- const limit = normalizeConnectorLimit(params?.limit);
201
- const target = params?.target ?? context.target;
202
- if (target?.roomId) {
203
- return readStoredMessageMemories(context.runtime, target.roomId, limit);
204
- }
205
- const targets = (await service.listConnectorSpaces(accountId))
206
- .slice(0, 10)
207
- .map((space) => googleChatSpaceToConnectorTarget(space, 0.55, accountId));
208
- return readStoredMessagesForTargets(context.runtime, targets, limit);
209
- },
210
- searchMessages: async (context, params) => {
211
- const limit = normalizeConnectorLimit(params?.limit);
212
- const target = params?.target ?? context.target;
213
- const messages = target?.roomId
214
- ? await readStoredMessageMemories(context.runtime, target.roomId, Math.max(limit, 100))
215
- : await readStoredMessagesForTargets(context.runtime, (await service.listConnectorSpaces(accountId))
216
- .slice(0, 10)
217
- .map((space) => googleChatSpaceToConnectorTarget(space, 0.55, accountId)), Math.max(limit, 100));
218
- return filterMemoriesByQuery(messages, params.query, limit);
219
- },
220
- reactHandler: async (_handlerRuntime, params) => {
221
- const messageName = getMutationMessageName(params);
222
- const emoji = String(params.emoji ?? "").trim();
223
- if (!emoji) {
224
- throw new Error("Google Chat reactHandler requires emoji");
225
- }
226
- const result = await service.sendReaction(messageName, emoji, accountId);
227
- if (!result.success) {
228
- throw new Error(result.error || "Google Chat reaction failed");
229
- }
230
- },
231
- editHandler: async (_handlerRuntime, params) => {
232
- const messageName = getMutationMessageName(params);
233
- const mutationParams = params;
234
- const text = String(mutationParams.text ?? params.content?.text ?? "").trim();
235
- if (!text) {
236
- throw new Error("Google Chat editHandler requires text content");
237
- }
238
- const result = await service.updateMessage(messageName, text, accountId);
239
- if (!result.success) {
240
- throw new Error(result.error || "Google Chat message edit failed");
241
- }
242
- },
243
- deleteHandler: async (_handlerRuntime, params) => {
244
- const messageName = getMutationMessageName(params);
245
- const result = await service.deleteMessage(messageName, accountId);
246
- if (!result.success) {
247
- throw new Error(result.error || "Google Chat message delete failed");
248
- }
249
- },
250
- getChatContext: async (target, context) => {
251
- const room = target.roomId ? await context.runtime.getRoom(target.roomId) : null;
252
- const channelId = String(target.channelId ?? room?.channelId ?? "").trim();
253
- const spaceName = normalizeSpaceTarget(channelId);
254
- if (!spaceName) {
255
- return null;
256
- }
257
- const space = (await service.listConnectorSpaces(accountId)).find((candidate) => candidate.name === spaceName);
258
- if (!space) {
259
- return null;
260
- }
261
- return {
262
- target: {
263
- source: GOOGLE_CHAT_SERVICE_NAME,
264
- accountId,
265
- roomId: target.roomId,
266
- channelId: space.name,
267
- threadId: target.threadId,
268
- },
269
- label: getSpaceDisplayName(space),
270
- summary: isDirectMessage(space) ? "Google Chat direct message" : "Google Chat space",
271
- metadata: {
272
- accountId,
273
- spaceType: space.type,
274
- threaded: space.threaded,
275
- singleUserBotDm: space.singleUserBotDm,
276
- },
277
- };
278
- },
279
- getUserContext: async (entityId, context) => {
280
- const entity = typeof context.runtime.getEntityById === "function"
281
- ? await context.runtime.getEntityById(String(entityId))
282
- : null;
283
- if (!entity) {
284
- return null;
285
- }
286
- return {
287
- entityId,
288
- label: entity.names?.[0],
289
- aliases: entity.names,
290
- handles: {},
291
- metadata: entity.metadata,
292
- };
293
- },
294
- };
295
- runtime.registerMessageConnector(registration);
296
- return;
297
- }
298
- runtime.registerSendHandler(GOOGLE_CHAT_SERVICE_NAME, sendHandler);
299
- }
300
- async stop() {
301
- logger.info("Stopping Google Chat service...");
302
- for (const state of this.states.values()) {
303
- state.connected = false;
304
- }
305
- logger.info("Google Chat service stopped");
306
- }
307
- loadSettings(accountId) {
308
- const runtime = this.runtime;
309
- if (!runtime) {
310
- throw new GoogleChatConfigurationError("Runtime not initialized");
311
- }
312
- return resolveGoogleChatAccountSettings(runtime, accountId);
313
- }
314
- validateSettings(settings) {
315
- if (!settings.serviceAccount && !settings.serviceAccountFile) {
316
- if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
317
- throw new GoogleChatConfigurationError("Google Chat requires service account credentials. Set GOOGLE_CHAT_SERVICE_ACCOUNT, GOOGLE_CHAT_SERVICE_ACCOUNT_FILE, or GOOGLE_APPLICATION_CREDENTIALS.", "GOOGLE_CHAT_SERVICE_ACCOUNT");
318
- }
319
- }
320
- if (!settings.audience) {
321
- throw new GoogleChatConfigurationError("GOOGLE_CHAT_AUDIENCE is required for webhook verification", "GOOGLE_CHAT_AUDIENCE");
322
- }
323
- if (!["app-url", "project-number"].includes(settings.audienceType)) {
324
- throw new GoogleChatConfigurationError("GOOGLE_CHAT_AUDIENCE_TYPE must be 'app-url' or 'project-number'", "GOOGLE_CHAT_AUDIENCE_TYPE");
325
- }
326
- }
327
- createAuth(settings) {
328
- if (settings.serviceAccountFile) {
329
- return new GoogleAuth({
330
- keyFile: settings.serviceAccountFile,
331
- scopes: [CHAT_SCOPE],
332
- });
333
- }
334
- if (settings.serviceAccount) {
335
- const credentials = JSON.parse(settings.serviceAccount);
336
- return new GoogleAuth({
337
- credentials,
338
- scopes: [CHAT_SCOPE],
339
- });
340
- }
341
- return new GoogleAuth({
342
- scopes: [CHAT_SCOPE],
343
- });
344
- }
345
- async testConnection(state = this.getState()) {
346
- const token = await this.getAccessToken(state.accountId);
347
- if (!token) {
348
- throw new GoogleChatAuthenticationError("Failed to obtain access token");
349
- }
350
- const url = `${CHAT_API_BASE}/spaces?pageSize=1`;
351
- const response = await fetch(url, {
352
- headers: {
353
- Authorization: `Bearer ${token}`,
354
- "Content-Type": "application/json",
355
- },
356
- });
357
- if (!response.ok) {
358
- const text = await response.text().catch(() => "");
359
- throw new GoogleChatApiError(`Failed to connect to Google Chat API: ${text || response.statusText}`, response.status);
360
- }
361
- logger.info("Google Chat API connection verified");
362
- }
363
- isConnected() {
364
- const legacy = this;
365
- const states = this.states ?? new Map();
366
- if (states.size === 0 && typeof legacy.connected === "boolean") {
367
- return legacy.connected;
368
- }
369
- return Array.from(states.values()).some((state) => state.connected);
370
- }
371
- getAccountId(runtime) {
372
- const legacy = this;
373
- const states = this.states ?? new Map();
374
- if (states.size === 0 && legacy.settings?.accountId) {
375
- return normalizeGoogleChatAccountId(legacy.settings.accountId);
376
- }
377
- return normalizeGoogleChatAccountId(this.defaultAccountId !== DEFAULT_GOOGLE_CHAT_ACCOUNT_ID
378
- ? this.defaultAccountId
379
- : runtime
380
- ? resolveDefaultGoogleChatAccountId(runtime)
381
- : this.defaultAccountId);
382
- }
383
- getBotUser() {
384
- return this.getState().settings.botUser;
385
- }
386
- async getAccessToken(accountId) {
387
- const state = this.getState(accountId);
388
- const client = await state.auth.getClient();
389
- const tokenResponse = await client.getAccessToken();
390
- const token = typeof tokenResponse === "string" ? tokenResponse : tokenResponse?.token;
391
- if (!token) {
392
- throw new GoogleChatAuthenticationError("Failed to obtain access token");
393
- }
394
- return token;
395
- }
396
- async fetchApi(url, init = {}, accountId) {
397
- const token = await this.getAccessToken(accountId);
398
- const response = await fetch(url, {
399
- ...init,
400
- headers: {
401
- ...init.headers,
402
- Authorization: `Bearer ${token}`,
403
- "Content-Type": "application/json",
404
- },
405
- });
406
- if (!response.ok) {
407
- const text = await response.text().catch(() => "");
408
- throw new GoogleChatApiError(`Google Chat API error: ${text || response.statusText}`, response.status);
409
- }
410
- return (await response.json());
411
- }
412
- async getSpaces(accountId) {
413
- const state = this.getState(accountId);
414
- const url = `${CHAT_API_BASE}/spaces`;
415
- const response = await this.fetchApi(url, {}, state.accountId);
416
- state.cachedSpaces = response.spaces || [];
417
- return state.cachedSpaces;
418
- }
419
- async sendMessage(options) {
420
- const state = this.getState(options.accountId);
421
- if (!options.space) {
422
- return {
423
- success: false,
424
- error: "Space is required",
425
- };
426
- }
427
- const body = {};
428
- if (options.text) {
429
- body.text = options.text;
430
- }
431
- if (options.thread) {
432
- body.thread = { name: options.thread };
433
- }
434
- if (options.attachments && options.attachments.length > 0) {
435
- body.attachment = options.attachments.map((att) => ({
436
- attachmentDataRef: { attachmentUploadToken: att.attachmentUploadToken },
437
- ...(att.contentName ? { contentName: att.contentName } : {}),
438
- }));
439
- }
440
- const url = `${CHAT_API_BASE}/${options.space}/messages`;
441
- const result = await this.fetchApi(url, {
442
- method: "POST",
443
- body: JSON.stringify(body),
444
- }, state.accountId);
445
- logger.debug(`Message sent to ${options.space}: ${result.name}`);
446
- if (this.runtime) {
447
- this.runtime.emitEvent(GoogleChatEventTypes.MESSAGE_SENT, {
448
- runtime: this.runtime,
449
- accountId: state.accountId,
450
- messageName: result.name,
451
- space: options.space,
452
- });
453
- }
454
- return {
455
- success: true,
456
- messageName: result.name,
457
- space: options.space,
458
- };
459
- }
460
- async updateMessage(messageName, text, accountId) {
461
- const url = `${CHAT_API_BASE}/${messageName}?updateMask=text`;
462
- const result = await this.fetchApi(url, {
463
- method: "PATCH",
464
- body: JSON.stringify({ text }),
465
- }, accountId);
466
- return {
467
- success: true,
468
- messageName: result.name,
469
- };
470
- }
471
- async deleteMessage(messageName, accountId) {
472
- const url = `${CHAT_API_BASE}/${messageName}`;
473
- const token = await this.getAccessToken(accountId);
474
- const response = await fetch(url, {
475
- method: "DELETE",
476
- headers: {
477
- Authorization: `Bearer ${token}`,
478
- },
479
- });
480
- if (!response.ok) {
481
- const text = await response.text().catch(() => "");
482
- return {
483
- success: false,
484
- error: `Failed to delete message: ${text || response.statusText}`,
485
- };
486
- }
487
- return { success: true };
488
- }
489
- async sendReaction(messageName, emoji, accountId) {
490
- const state = this.getState(accountId);
491
- const url = `${CHAT_API_BASE}/${messageName}/reactions`;
492
- const result = await this.fetchApi(url, {
493
- method: "POST",
494
- body: JSON.stringify({ emoji: { unicode: emoji } }),
495
- }, state.accountId);
496
- if (this.runtime) {
497
- this.runtime.emitEvent(GoogleChatEventTypes.REACTION_SENT, {
498
- runtime: this.runtime,
499
- accountId: state.accountId,
500
- messageName,
501
- emoji,
502
- reactionName: result.name,
503
- });
504
- }
505
- return {
506
- success: true,
507
- name: result.name,
508
- };
509
- }
510
- async deleteReaction(reactionName, accountId) {
511
- const url = `${CHAT_API_BASE}/${reactionName}`;
512
- const token = await this.getAccessToken(accountId);
513
- const response = await fetch(url, {
514
- method: "DELETE",
515
- headers: {
516
- Authorization: `Bearer ${token}`,
517
- },
518
- });
519
- if (!response.ok) {
520
- const text = await response.text().catch(() => "");
521
- return {
522
- success: false,
523
- error: `Failed to delete reaction: ${text || response.statusText}`,
524
- };
525
- }
526
- return { success: true };
527
- }
528
- async listReactions(messageName, limit, accountId) {
529
- const url = new URL(`${CHAT_API_BASE}/${messageName}/reactions`);
530
- if (limit && limit > 0) {
531
- url.searchParams.set("pageSize", String(limit));
532
- }
533
- const result = await this.fetchApi(url.toString(), {}, accountId);
534
- return result.reactions || [];
535
- }
536
- async findDirectMessage(userName, accountId) {
537
- const url = new URL(`${CHAT_API_BASE}/spaces:findDirectMessage`);
538
- url.searchParams.set("name", userName);
539
- const result = await this.fetchApi(url.toString(), {}, accountId);
540
- return result;
541
- }
542
- async uploadAttachment(space, filename, buffer, contentType, accountId) {
543
- const boundary = `elizaos-${crypto.randomUUID()}`;
544
- const metadata = JSON.stringify({ filename });
545
- const header = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n`;
546
- const mediaHeader = `--${boundary}\r\nContent-Type: ${contentType || "application/octet-stream"}\r\n\r\n`;
547
- const footer = `\r\n--${boundary}--\r\n`;
548
- const body = Buffer.concat([
549
- Buffer.from(header, "utf8"),
550
- Buffer.from(mediaHeader, "utf8"),
551
- buffer,
552
- Buffer.from(footer, "utf8"),
553
- ]);
554
- const token = await this.getAccessToken(accountId);
555
- const url = `${CHAT_UPLOAD_BASE}/${space}/attachments:upload?uploadType=multipart`;
556
- const response = await fetch(url, {
557
- method: "POST",
558
- headers: {
559
- Authorization: `Bearer ${token}`,
560
- "Content-Type": `multipart/related; boundary=${boundary}`,
561
- },
562
- body,
563
- });
564
- if (!response.ok) {
565
- const text = await response.text().catch(() => "");
566
- throw new GoogleChatApiError(`Failed to upload attachment: ${text || response.statusText}`, response.status);
567
- }
568
- const payload = (await response.json());
569
- return {
570
- attachmentUploadToken: payload.attachmentDataRef?.attachmentUploadToken,
571
- };
572
- }
573
- async downloadMedia(resourceName, maxBytes, accountId) {
574
- const url = `${CHAT_API_BASE}/media/${resourceName}?alt=media`;
575
- const token = await this.getAccessToken(accountId);
576
- const response = await fetch(url, {
577
- headers: {
578
- Authorization: `Bearer ${token}`,
579
- },
580
- });
581
- if (!response.ok) {
582
- const text = await response.text().catch(() => "");
583
- throw new GoogleChatApiError(`Failed to download media: ${text || response.statusText}`, response.status);
584
- }
585
- const contentLength = response.headers.get("content-length");
586
- if (maxBytes && contentLength) {
587
- const length = Number(contentLength);
588
- if (Number.isFinite(length) && length > maxBytes) {
589
- throw new GoogleChatApiError(`Media exceeds max bytes (${maxBytes})`, 413);
590
- }
591
- }
592
- const arrayBuffer = await response.arrayBuffer();
593
- const buffer = Buffer.from(arrayBuffer);
594
- const contentType = response.headers.get("content-type") || undefined;
595
- return { buffer, contentType };
596
- }
597
- getSettings() {
598
- try {
599
- return this.getState().settings;
600
- }
601
- catch {
602
- return null;
603
- }
604
- }
605
- async sendDirectMessage(target, content) {
606
- const accountId = readGoogleChatAccountId(content) ?? this.getAccountId();
607
- const userName = normalizeUserTarget(target);
608
- if (!userName) {
609
- throw new Error(`Invalid Google Chat user target: ${target}`);
610
- }
611
- const space = await this.findDirectMessage(userName, accountId);
612
- if (!space?.name) {
613
- throw new Error(`Could not resolve Google Chat direct message for ${target}`);
614
- }
615
- await this.sendConnectorContent(space.name, content, undefined, accountId);
616
- }
617
- async sendRoomMessage(target, content) {
618
- const accountId = readGoogleChatAccountId(content) ?? this.getAccountId();
619
- const spaceName = normalizeSpaceTarget(target);
620
- if (!spaceName) {
621
- throw new Error(`Invalid Google Chat space target: ${target}`);
622
- }
623
- await this.sendConnectorContent(spaceName, content, undefined, accountId);
624
- }
625
- async handleSendMessage(runtime, target, content) {
626
- const requestedAccountId = normalizeGoogleChatAccountId(target.accountId ?? readGoogleChatAccountId(content, target) ?? this.getAccountId());
627
- this.getState(requestedAccountId);
628
- const room = target.roomId ? await runtime.getRoom(target.roomId) : null;
629
- const channelId = String(target.channelId ?? room?.channelId ?? "").trim();
630
- if (!channelId) {
631
- throw new Error("Google Chat target is missing a space or user resource name");
632
- }
633
- const userName = normalizeUserTarget(channelId);
634
- if (userName && !channelId.startsWith("spaces/")) {
635
- await this.sendConnectorDirectMessage(userName, content, requestedAccountId);
636
- return;
637
- }
638
- const spaceName = normalizeSpaceTarget(channelId);
639
- if (!spaceName) {
640
- throw new Error(`Invalid Google Chat target: ${channelId}`);
641
- }
642
- await this.sendConnectorContent(spaceName, content, target, requestedAccountId);
643
- }
644
- async sendConnectorDirectMessage(userName, content, accountId) {
645
- const space = await this.findDirectMessage(userName, accountId);
646
- if (!space?.name) {
647
- throw new Error(`Could not resolve Google Chat direct message for ${userName}`);
648
- }
649
- await this.sendConnectorContent(space.name, content, undefined, accountId);
650
- }
651
- async sendConnectorContent(space, content, target, accountId) {
652
- const text = typeof content.text === "string" ? content.text.trim() : "";
653
- const options = googleChatOptionsFromContent(space, { ...content, text }, target);
654
- if (!options.text && (!options.attachments || options.attachments.length === 0)) {
655
- return;
656
- }
657
- const result = await this.sendMessage({ ...options, accountId });
658
- if (!result.success) {
659
- throw new Error(result.error || "Google Chat message send failed");
660
- }
661
- }
662
- async listConnectorSpaces(accountId) {
663
- const state = this.getState(accountId);
664
- try {
665
- return await this.getSpaces(state.accountId);
666
- }
667
- catch {
668
- return [...state.cachedSpaces];
669
- }
670
- }
671
- async processWebhookEvent(event, accountId) {
672
- const eventType = event.type;
673
- const resolvedAccountId = normalizeGoogleChatAccountId(accountId ?? readGoogleChatAccountId(event) ?? this.getAccountId());
674
- this.getState(resolvedAccountId);
675
- if (!this.runtime)
676
- return;
677
- if (eventType === "MESSAGE") {
678
- this.runtime.emitEvent(GoogleChatEventTypes.MESSAGE_RECEIVED, {
679
- runtime: this.runtime,
680
- accountId: resolvedAccountId,
681
- event,
682
- message: event.message,
683
- space: event.space,
684
- user: event.user,
685
- });
686
- }
687
- else if (eventType === "ADDED_TO_SPACE") {
688
- this.runtime.emitEvent(GoogleChatEventTypes.SPACE_JOINED, {
689
- runtime: this.runtime,
690
- accountId: resolvedAccountId,
691
- space: event.space,
692
- user: event.user,
693
- });
694
- }
695
- else if (eventType === "REMOVED_FROM_SPACE") {
696
- this.runtime.emitEvent(GoogleChatEventTypes.SPACE_LEFT, {
697
- runtime: this.runtime,
698
- accountId: resolvedAccountId,
699
- space: event.space,
700
- user: event.user,
701
- });
702
- }
703
- }
704
- getState(accountId = this.defaultAccountId) {
705
- const normalized = normalizeGoogleChatAccountId(accountId);
706
- const states = this.states ?? new Map();
707
- const state = states.get(normalized);
708
- if (state) {
709
- return state;
710
- }
711
- const legacy = this;
712
- if (legacy.settings) {
713
- return {
714
- accountId: normalizeGoogleChatAccountId(legacy.settings.accountId ?? normalized),
715
- settings: legacy.settings,
716
- auth: legacy.auth ?? this.createAuth(legacy.settings),
717
- connected: legacy.connected ?? true,
718
- cachedSpaces: legacy.cachedSpaces ?? [],
719
- };
720
- }
721
- throw new Error(`Google Chat account '${normalized}' is not available in this service instance`);
722
- }
723
- }
724
- //# sourceMappingURL=service.js.map