@kodelyth/googlechat 2026.5.42 → 2026.6.1

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 (61) hide show
  1. package/klaw.plugin.json +967 -2
  2. package/package.json +16 -4
  3. package/api.ts +0 -3
  4. package/channel-config-api.ts +0 -1
  5. package/channel-plugin-api.ts +0 -1
  6. package/config-api.ts +0 -2
  7. package/contract-api.ts +0 -5
  8. package/doctor-contract-api.ts +0 -1
  9. package/index.ts +0 -20
  10. package/runtime-api.ts +0 -55
  11. package/secret-contract-api.ts +0 -5
  12. package/setup-entry.ts +0 -13
  13. package/setup-plugin-api.ts +0 -3
  14. package/src/accounts.ts +0 -181
  15. package/src/actions.test.ts +0 -289
  16. package/src/actions.ts +0 -227
  17. package/src/api.ts +0 -316
  18. package/src/approval-auth.test.ts +0 -24
  19. package/src/approval-auth.ts +0 -32
  20. package/src/auth.ts +0 -218
  21. package/src/channel-config.test.ts +0 -39
  22. package/src/channel.adapters.ts +0 -340
  23. package/src/channel.deps.runtime.ts +0 -29
  24. package/src/channel.runtime.ts +0 -17
  25. package/src/channel.setup.ts +0 -98
  26. package/src/channel.test.ts +0 -784
  27. package/src/channel.ts +0 -277
  28. package/src/config-schema.test.ts +0 -31
  29. package/src/config-schema.ts +0 -3
  30. package/src/doctor-contract.test.ts +0 -75
  31. package/src/doctor-contract.ts +0 -182
  32. package/src/doctor.ts +0 -57
  33. package/src/gateway.ts +0 -63
  34. package/src/google-auth.runtime.test.ts +0 -543
  35. package/src/google-auth.runtime.ts +0 -568
  36. package/src/group-policy.ts +0 -17
  37. package/src/monitor-access.test.ts +0 -491
  38. package/src/monitor-access.ts +0 -465
  39. package/src/monitor-durable.test.ts +0 -39
  40. package/src/monitor-durable.ts +0 -23
  41. package/src/monitor-reply-delivery.ts +0 -156
  42. package/src/monitor-routing.ts +0 -65
  43. package/src/monitor-types.ts +0 -33
  44. package/src/monitor-webhook.test.ts +0 -587
  45. package/src/monitor-webhook.ts +0 -303
  46. package/src/monitor.reply-delivery.test.ts +0 -144
  47. package/src/monitor.test.ts +0 -159
  48. package/src/monitor.ts +0 -527
  49. package/src/monitor.webhook-routing.test.ts +0 -257
  50. package/src/runtime.ts +0 -9
  51. package/src/secret-contract.test.ts +0 -60
  52. package/src/secret-contract.ts +0 -161
  53. package/src/setup-core.ts +0 -40
  54. package/src/setup-surface.ts +0 -243
  55. package/src/setup.test.ts +0 -619
  56. package/src/targets.test.ts +0 -453
  57. package/src/targets.ts +0 -66
  58. package/src/types.config.ts +0 -3
  59. package/src/types.ts +0 -73
  60. package/test-api.ts +0 -2
  61. package/tsconfig.json +0 -16
package/src/actions.ts DELETED
@@ -1,227 +0,0 @@
1
- import {
2
- createActionGate,
3
- jsonResult,
4
- readNumberParam,
5
- readReactionParams,
6
- readStringParam,
7
- } from "klaw/plugin-sdk/channel-actions";
8
- import type {
9
- ChannelMessageActionAdapter,
10
- ChannelMessageActionName,
11
- } from "klaw/plugin-sdk/channel-contract";
12
- import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
13
- import { loadOutboundMediaFromUrl } from "klaw/plugin-sdk/outbound-media";
14
- import { extractToolSend } from "klaw/plugin-sdk/tool-send";
15
- import { listEnabledGoogleChatAccounts, resolveGoogleChatAccount } from "./accounts.js";
16
- import {
17
- createGoogleChatReaction,
18
- deleteGoogleChatReaction,
19
- listGoogleChatReactions,
20
- sendGoogleChatMessage,
21
- uploadGoogleChatAttachment,
22
- } from "./api.js";
23
- import { getGoogleChatRuntime } from "./runtime.js";
24
- import { resolveGoogleChatOutboundSpace } from "./targets.js";
25
-
26
- const providerId = "googlechat";
27
-
28
- function listEnabledAccounts(cfg: KlawConfig) {
29
- return listEnabledGoogleChatAccounts(cfg).filter(
30
- (account) => account.enabled && account.credentialSource !== "none",
31
- );
32
- }
33
-
34
- function isReactionsEnabled(accounts: Array<{ config: { actions?: unknown } }>) {
35
- for (const account of accounts) {
36
- const gate = createActionGate(account.config.actions as Record<string, boolean | undefined>);
37
- if (gate("reactions")) {
38
- return true;
39
- }
40
- }
41
- return false;
42
- }
43
-
44
- function resolveAppUserNames(account: { config: { botUser?: string | null } }) {
45
- return new Set(["users/app", account.config.botUser?.trim()].filter(Boolean) as string[]);
46
- }
47
-
48
- async function loadGoogleChatActionMedia(params: {
49
- mediaUrl: string;
50
- maxBytes: number;
51
- mediaAccess?: {
52
- localRoots?: readonly string[];
53
- readFile?: (filePath: string) => Promise<Buffer>;
54
- };
55
- mediaLocalRoots?: readonly string[];
56
- mediaReadFile?: (filePath: string) => Promise<Buffer>;
57
- }) {
58
- const runtime = getGoogleChatRuntime();
59
- return /^https?:\/\//i.test(params.mediaUrl)
60
- ? await runtime.channel.media.readRemoteMediaBuffer({
61
- url: params.mediaUrl,
62
- maxBytes: params.maxBytes,
63
- })
64
- : await loadOutboundMediaFromUrl(params.mediaUrl, {
65
- maxBytes: params.maxBytes,
66
- mediaAccess: params.mediaAccess,
67
- mediaLocalRoots: params.mediaLocalRoots,
68
- mediaReadFile: params.mediaReadFile,
69
- });
70
- }
71
-
72
- export const googlechatMessageActions: ChannelMessageActionAdapter = {
73
- describeMessageTool: ({ cfg, accountId }) => {
74
- const accounts = accountId
75
- ? [resolveGoogleChatAccount({ cfg, accountId })].filter(
76
- (account) => account.enabled && account.credentialSource !== "none",
77
- )
78
- : listEnabledAccounts(cfg);
79
- if (accounts.length === 0) {
80
- return null;
81
- }
82
- const actions = new Set<ChannelMessageActionName>([]);
83
- actions.add("send");
84
- actions.add("upload-file");
85
- if (isReactionsEnabled(accounts)) {
86
- actions.add("react");
87
- actions.add("reactions");
88
- }
89
- return { actions: Array.from(actions) };
90
- },
91
- extractToolSend: ({ args }) => {
92
- return extractToolSend(args, "sendMessage");
93
- },
94
- handleAction: async ({
95
- action,
96
- params,
97
- cfg,
98
- accountId,
99
- mediaAccess,
100
- mediaLocalRoots,
101
- mediaReadFile,
102
- }) => {
103
- const account = resolveGoogleChatAccount({
104
- cfg: cfg,
105
- accountId,
106
- });
107
- if (account.credentialSource === "none") {
108
- throw new Error("Google Chat credentials are missing.");
109
- }
110
-
111
- if (action === "send" || action === "upload-file") {
112
- const to = readStringParam(params, "to", { required: true });
113
- const content =
114
- readStringParam(params, "message", {
115
- required: action === "send",
116
- allowEmpty: true,
117
- }) ??
118
- readStringParam(params, "initialComment", {
119
- allowEmpty: true,
120
- }) ??
121
- "";
122
- const mediaUrl =
123
- readStringParam(params, "media", { trim: false }) ??
124
- readStringParam(params, "filePath", { trim: false }) ??
125
- readStringParam(params, "path", { trim: false });
126
- const threadId = readStringParam(params, "threadId") ?? readStringParam(params, "replyTo");
127
- const space = await resolveGoogleChatOutboundSpace({ account, target: to });
128
-
129
- if (mediaUrl) {
130
- const maxBytes = (account.config.mediaMaxMb ?? 20) * 1024 * 1024;
131
- const loaded = await loadGoogleChatActionMedia({
132
- mediaUrl,
133
- maxBytes,
134
- mediaAccess,
135
- mediaLocalRoots,
136
- mediaReadFile,
137
- });
138
- const uploadFileName =
139
- readStringParam(params, "filename") ??
140
- readStringParam(params, "title") ??
141
- loaded.fileName ??
142
- "attachment";
143
- const upload = await uploadGoogleChatAttachment({
144
- account,
145
- space,
146
- filename: uploadFileName,
147
- buffer: loaded.buffer,
148
- contentType: loaded.contentType,
149
- });
150
- await sendGoogleChatMessage({
151
- account,
152
- space,
153
- text: content,
154
- thread: threadId ?? undefined,
155
- attachments: upload.attachmentUploadToken
156
- ? [
157
- {
158
- attachmentUploadToken: upload.attachmentUploadToken,
159
- contentName: uploadFileName,
160
- },
161
- ]
162
- : undefined,
163
- });
164
- return jsonResult({ ok: true, to: space });
165
- }
166
-
167
- if (action === "upload-file") {
168
- throw new Error("upload-file requires media, filePath, or path");
169
- }
170
-
171
- await sendGoogleChatMessage({
172
- account,
173
- space,
174
- text: content,
175
- thread: threadId ?? undefined,
176
- });
177
- return jsonResult({ ok: true, to: space });
178
- }
179
-
180
- if (action === "react") {
181
- const messageName = readStringParam(params, "messageId", { required: true });
182
- const { emoji, remove, isEmpty } = readReactionParams(params, {
183
- removeErrorMessage: "Emoji is required to remove a Google Chat reaction.",
184
- });
185
- if (remove || isEmpty) {
186
- const reactions = await listGoogleChatReactions({ account, messageName });
187
- const appUsers = resolveAppUserNames(account);
188
- const toRemove = reactions.filter((reaction) => {
189
- const userName = reaction.user?.name?.trim();
190
- if (appUsers.size > 0 && !appUsers.has(userName ?? "")) {
191
- return false;
192
- }
193
- if (emoji) {
194
- return reaction.emoji?.unicode === emoji;
195
- }
196
- return true;
197
- });
198
- for (const reaction of toRemove) {
199
- if (!reaction.name) {
200
- continue;
201
- }
202
- await deleteGoogleChatReaction({ account, reactionName: reaction.name });
203
- }
204
- return jsonResult({ ok: true, removed: toRemove.length });
205
- }
206
- const reaction = await createGoogleChatReaction({
207
- account,
208
- messageName,
209
- emoji,
210
- });
211
- return jsonResult({ ok: true, reaction });
212
- }
213
-
214
- if (action === "reactions") {
215
- const messageName = readStringParam(params, "messageId", { required: true });
216
- const limit = readNumberParam(params, "limit", { integer: true });
217
- const reactions = await listGoogleChatReactions({
218
- account,
219
- messageName,
220
- limit: limit ?? undefined,
221
- });
222
- return jsonResult({ ok: true, reactions });
223
- }
224
-
225
- throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
226
- },
227
- };
package/src/api.ts DELETED
@@ -1,316 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
3
- import { readResponseWithLimit } from "klaw/plugin-sdk/response-limit-runtime";
4
- import { fetchWithSsrFGuard } from "klaw/plugin-sdk/ssrf-runtime";
5
- import type { ResolvedGoogleChatAccount } from "./accounts.js";
6
- import { getGoogleChatAccessToken } from "./auth.js";
7
- import type { GoogleChatReaction } from "./types.js";
8
-
9
- const CHAT_API_BASE = "https://chat.googleapis.com/v1";
10
- const CHAT_UPLOAD_BASE = "https://chat.googleapis.com/upload/v1";
11
-
12
- async function readGoogleChatJsonResponse<T>(response: Response, label: string): Promise<T> {
13
- try {
14
- return (await response.json()) as T;
15
- } catch (cause) {
16
- throw new Error(`${label}: malformed JSON response`, { cause });
17
- }
18
- }
19
-
20
- const headersToObject = (headers?: HeadersInit): Record<string, string> =>
21
- headers instanceof Headers
22
- ? Object.fromEntries(headers.entries())
23
- : Array.isArray(headers)
24
- ? Object.fromEntries(headers)
25
- : headers || {};
26
-
27
- async function withGoogleChatResponse<T>(params: {
28
- account: ResolvedGoogleChatAccount;
29
- url: string;
30
- init?: RequestInit;
31
- auditContext: string;
32
- errorPrefix?: string;
33
- handleResponse: (response: Response) => Promise<T>;
34
- }): Promise<T> {
35
- const {
36
- account,
37
- url,
38
- init,
39
- auditContext,
40
- errorPrefix = "Google Chat API",
41
- handleResponse,
42
- } = params;
43
- const token = await getGoogleChatAccessToken(account);
44
- const { response, release } = await fetchWithSsrFGuard({
45
- url,
46
- init: {
47
- ...init,
48
- headers: {
49
- ...headersToObject(init?.headers),
50
- Authorization: `Bearer ${token}`,
51
- },
52
- },
53
- auditContext,
54
- });
55
- try {
56
- if (!response.ok) {
57
- const text = await response.text().catch(() => "");
58
- throw new Error(`${errorPrefix} ${response.status}: ${text || response.statusText}`);
59
- }
60
- return await handleResponse(response);
61
- } finally {
62
- await release();
63
- }
64
- }
65
-
66
- async function fetchJson<T>(
67
- account: ResolvedGoogleChatAccount,
68
- url: string,
69
- init: RequestInit,
70
- ): Promise<T> {
71
- return await withGoogleChatResponse({
72
- account,
73
- url,
74
- init: {
75
- ...init,
76
- headers: {
77
- ...headersToObject(init.headers),
78
- "Content-Type": "application/json",
79
- },
80
- },
81
- auditContext: "googlechat.api.json",
82
- handleResponse: async (response) =>
83
- await readGoogleChatJsonResponse<T>(response, "Google Chat API request failed"),
84
- });
85
- }
86
-
87
- async function fetchOk(
88
- account: ResolvedGoogleChatAccount,
89
- url: string,
90
- init: RequestInit,
91
- ): Promise<void> {
92
- await withGoogleChatResponse({
93
- account,
94
- url,
95
- init,
96
- auditContext: "googlechat.api.ok",
97
- handleResponse: async () => undefined,
98
- });
99
- }
100
-
101
- async function fetchBuffer(
102
- account: ResolvedGoogleChatAccount,
103
- url: string,
104
- init?: RequestInit,
105
- options?: { maxBytes?: number },
106
- ): Promise<{ buffer: Buffer; contentType?: string }> {
107
- return await withGoogleChatResponse({
108
- account,
109
- url,
110
- init,
111
- auditContext: "googlechat.api.buffer",
112
- handleResponse: async (res) => {
113
- const maxBytes = options?.maxBytes;
114
- const lengthHeader = res.headers.get("content-length");
115
- if (maxBytes && lengthHeader) {
116
- const length = Number(lengthHeader);
117
- if (Number.isFinite(length) && length > maxBytes) {
118
- throw new Error(`Google Chat media exceeds max bytes (${maxBytes})`);
119
- }
120
- }
121
- if (!maxBytes) {
122
- const buffer = Buffer.from(await res.arrayBuffer());
123
- const contentType = res.headers.get("content-type") ?? undefined;
124
- return { buffer, contentType };
125
- }
126
- const buffer = await readResponseWithLimit(res, maxBytes, {
127
- onOverflow: () => new Error(`Google Chat media exceeds max bytes (${maxBytes})`),
128
- });
129
- const contentType = res.headers.get("content-type") ?? undefined;
130
- return { buffer, contentType };
131
- },
132
- });
133
- }
134
-
135
- export async function sendGoogleChatMessage(params: {
136
- account: ResolvedGoogleChatAccount;
137
- space: string;
138
- text?: string;
139
- thread?: string;
140
- attachments?: Array<{ attachmentUploadToken: string; contentName?: string }>;
141
- }): Promise<{ messageName?: string } | null> {
142
- const { account, space, text, thread, attachments } = params;
143
- const body: Record<string, unknown> = {};
144
- if (text) {
145
- body.text = text;
146
- }
147
- if (thread) {
148
- body.thread = { name: thread };
149
- }
150
- if (attachments && attachments.length > 0) {
151
- body.attachment = attachments.map((item) =>
152
- Object.assign(
153
- { attachmentDataRef: { attachmentUploadToken: item.attachmentUploadToken } },
154
- item.contentName ? { contentName: item.contentName } : {},
155
- ),
156
- );
157
- }
158
- const urlObj = new URL(`${CHAT_API_BASE}/${space}/messages`);
159
- if (thread) {
160
- urlObj.searchParams.set("messageReplyOption", "REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD");
161
- }
162
- const url = urlObj.toString();
163
- const result = await fetchJson<{ name?: string }>(account, url, {
164
- method: "POST",
165
- body: JSON.stringify(body),
166
- });
167
- return result ? { messageName: result.name } : null;
168
- }
169
-
170
- export async function updateGoogleChatMessage(params: {
171
- account: ResolvedGoogleChatAccount;
172
- messageName: string;
173
- text: string;
174
- }): Promise<{ messageName?: string }> {
175
- const { account, messageName, text } = params;
176
- const url = `${CHAT_API_BASE}/${messageName}?updateMask=text`;
177
- const result = await fetchJson<{ name?: string }>(account, url, {
178
- method: "PATCH",
179
- body: JSON.stringify({ text }),
180
- });
181
- return { messageName: result.name };
182
- }
183
-
184
- export async function deleteGoogleChatMessage(params: {
185
- account: ResolvedGoogleChatAccount;
186
- messageName: string;
187
- }): Promise<void> {
188
- const { account, messageName } = params;
189
- const url = `${CHAT_API_BASE}/${messageName}`;
190
- await fetchOk(account, url, { method: "DELETE" });
191
- }
192
-
193
- export async function uploadGoogleChatAttachment(params: {
194
- account: ResolvedGoogleChatAccount;
195
- space: string;
196
- filename: string;
197
- buffer: Buffer;
198
- contentType?: string;
199
- }): Promise<{ attachmentUploadToken?: string }> {
200
- const { account, space, filename, buffer, contentType } = params;
201
- const boundary = `klaw-${crypto.randomUUID()}`;
202
- const metadata = JSON.stringify({ filename });
203
- const header = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n`;
204
- const mediaHeader = `--${boundary}\r\nContent-Type: ${contentType ?? "application/octet-stream"}\r\n\r\n`;
205
- const footer = `\r\n--${boundary}--\r\n`;
206
- const body = Buffer.concat([
207
- Buffer.from(header, "utf8"),
208
- Buffer.from(mediaHeader, "utf8"),
209
- buffer,
210
- Buffer.from(footer, "utf8"),
211
- ]);
212
-
213
- const url = `${CHAT_UPLOAD_BASE}/${space}/attachments:upload?uploadType=multipart`;
214
- const payload = await withGoogleChatResponse<{
215
- attachmentDataRef?: { attachmentUploadToken?: string };
216
- }>({
217
- account,
218
- url,
219
- init: {
220
- method: "POST",
221
- headers: {
222
- "Content-Type": `multipart/related; boundary=${boundary}`,
223
- },
224
- body,
225
- },
226
- auditContext: "googlechat.upload",
227
- errorPrefix: "Google Chat upload",
228
- handleResponse: async (response) =>
229
- await readGoogleChatJsonResponse<{
230
- attachmentDataRef?: { attachmentUploadToken?: string };
231
- }>(response, "Google Chat upload failed"),
232
- });
233
- return {
234
- attachmentUploadToken: payload.attachmentDataRef?.attachmentUploadToken,
235
- };
236
- }
237
-
238
- export async function downloadGoogleChatMedia(params: {
239
- account: ResolvedGoogleChatAccount;
240
- resourceName: string;
241
- maxBytes?: number;
242
- }): Promise<{ buffer: Buffer; contentType?: string }> {
243
- const { account, resourceName, maxBytes } = params;
244
- const url = `${CHAT_API_BASE}/media/${resourceName}?alt=media`;
245
- return await fetchBuffer(account, url, undefined, { maxBytes });
246
- }
247
-
248
- export async function createGoogleChatReaction(params: {
249
- account: ResolvedGoogleChatAccount;
250
- messageName: string;
251
- emoji: string;
252
- }): Promise<GoogleChatReaction> {
253
- const { account, messageName, emoji } = params;
254
- const url = `${CHAT_API_BASE}/${messageName}/reactions`;
255
- return await fetchJson<GoogleChatReaction>(account, url, {
256
- method: "POST",
257
- body: JSON.stringify({ emoji: { unicode: emoji } }),
258
- });
259
- }
260
-
261
- export async function listGoogleChatReactions(params: {
262
- account: ResolvedGoogleChatAccount;
263
- messageName: string;
264
- limit?: number;
265
- }): Promise<GoogleChatReaction[]> {
266
- const { account, messageName, limit } = params;
267
- const url = new URL(`${CHAT_API_BASE}/${messageName}/reactions`);
268
- if (limit && limit > 0) {
269
- url.searchParams.set("pageSize", String(limit));
270
- }
271
- const result = await fetchJson<{ reactions?: GoogleChatReaction[] }>(account, url.toString(), {
272
- method: "GET",
273
- });
274
- return result.reactions ?? [];
275
- }
276
-
277
- export async function deleteGoogleChatReaction(params: {
278
- account: ResolvedGoogleChatAccount;
279
- reactionName: string;
280
- }): Promise<void> {
281
- const { account, reactionName } = params;
282
- const url = `${CHAT_API_BASE}/${reactionName}`;
283
- await fetchOk(account, url, { method: "DELETE" });
284
- }
285
-
286
- export async function findGoogleChatDirectMessage(params: {
287
- account: ResolvedGoogleChatAccount;
288
- userName: string;
289
- }): Promise<{ name?: string; displayName?: string } | null> {
290
- const { account, userName } = params;
291
- const url = new URL(`${CHAT_API_BASE}/spaces:findDirectMessage`);
292
- url.searchParams.set("name", userName);
293
- return await fetchJson<{ name?: string; displayName?: string }>(account, url.toString(), {
294
- method: "GET",
295
- });
296
- }
297
-
298
- export async function probeGoogleChat(account: ResolvedGoogleChatAccount): Promise<{
299
- ok: boolean;
300
- status?: number;
301
- error?: string;
302
- }> {
303
- try {
304
- const url = new URL(`${CHAT_API_BASE}/spaces`);
305
- url.searchParams.set("pageSize", "1");
306
- await fetchJson<Record<string, unknown>>(account, url.toString(), {
307
- method: "GET",
308
- });
309
- return { ok: true };
310
- } catch (err) {
311
- return {
312
- ok: false,
313
- error: formatErrorMessage(err),
314
- };
315
- }
316
- }
@@ -1,24 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { googleChatApprovalAuth } from "./approval-auth.js";
3
-
4
- describe("googleChatApprovalAuth", () => {
5
- it("authorizes stable users/* ids and ignores email-style approvers", () => {
6
- expect(
7
- googleChatApprovalAuth.authorizeActorAction({
8
- cfg: { channels: { googlechat: { dm: { allowFrom: ["users/123"] } } } },
9
- senderId: "users/123",
10
- action: "approve",
11
- approvalKind: "exec",
12
- }),
13
- ).toEqual({ authorized: true });
14
-
15
- expect(
16
- googleChatApprovalAuth.authorizeActorAction({
17
- cfg: { channels: { googlechat: { dm: { allowFrom: ["owner@example.com"] } } } },
18
- senderId: "users/attacker",
19
- action: "approve",
20
- approvalKind: "exec",
21
- }),
22
- ).toEqual({ authorized: true });
23
- });
24
- });
@@ -1,32 +0,0 @@
1
- import {
2
- createResolvedApproverActionAuthAdapter,
3
- resolveApprovalApprovers,
4
- } from "klaw/plugin-sdk/approval-auth-runtime";
5
- import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
6
- import { resolveGoogleChatAccount } from "./accounts.js";
7
- import { isGoogleChatUserTarget, normalizeGoogleChatTarget } from "./targets.js";
8
-
9
- function normalizeGoogleChatApproverId(value: string | number): string | undefined {
10
- const normalized = normalizeGoogleChatTarget(String(value));
11
- if (!normalized || !isGoogleChatUserTarget(normalized)) {
12
- return undefined;
13
- }
14
- const suffix = normalizeLowercaseStringOrEmpty(normalized.slice("users/".length));
15
- if (!suffix || suffix.includes("@")) {
16
- return undefined;
17
- }
18
- return `users/${suffix}`;
19
- }
20
-
21
- export const googleChatApprovalAuth = createResolvedApproverActionAuthAdapter({
22
- channelLabel: "Google Chat",
23
- resolveApprovers: ({ cfg, accountId }) => {
24
- const account = resolveGoogleChatAccount({ cfg, accountId }).config;
25
- return resolveApprovalApprovers({
26
- allowFrom: account.dm?.allowFrom,
27
- defaultTo: account.defaultTo,
28
- normalizeApprover: normalizeGoogleChatApproverId,
29
- });
30
- },
31
- normalizeSenderId: (value) => normalizeGoogleChatApproverId(value),
32
- });