import type { BotConfig } from "@hanzo/bot/plugin-sdk/googlechat"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId, normalizeOptionalAccountId, } from "@hanzo/bot/plugin-sdk/account-id"; import { isSecretRef } from "@hanzo/bot/plugin-sdk/googlechat"; import type { GoogleChatAccountConfig } from "./types.config.js"; export type GoogleChatCredentialSource = "file" | "inline" | "env" | "none"; export type ResolvedGoogleChatAccount = { accountId: string; name?: string; enabled: boolean; config: GoogleChatAccountConfig; credentialSource: GoogleChatCredentialSource; credentials?: Record; credentialsFile?: string; }; const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT"; const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"; function listConfiguredAccountIds(cfg: BotConfig): string[] { const accounts = cfg.channels?.["googlechat"]?.accounts; if (!accounts || typeof accounts !== "object") { return []; } return Object.keys(accounts).filter(Boolean); } export function listGoogleChatAccountIds(cfg: BotConfig): string[] { const ids = listConfiguredAccountIds(cfg); if (ids.length === 0) { return [DEFAULT_ACCOUNT_ID]; } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultGoogleChatAccountId(cfg: BotConfig): string { const channel = cfg.channels?.["googlechat"]; const preferred = normalizeOptionalAccountId(channel?.defaultAccount); if ( preferred && listGoogleChatAccountIds(cfg).some((accountId) => normalizeAccountId(accountId) === preferred) ) { return preferred; } const ids = listGoogleChatAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) { return DEFAULT_ACCOUNT_ID; } return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig( cfg: BotConfig, accountId: string, ): GoogleChatAccountConfig | undefined { const accounts = cfg.channels?.["googlechat"]?.accounts; if (!accounts || typeof accounts !== "object") { return undefined; } return accounts[accountId]; } function mergeGoogleChatAccountConfig(cfg: BotConfig, accountId: string): GoogleChatAccountConfig { const raw = cfg.channels?.["googlechat"] ?? {}; const { accounts: _ignored, defaultAccount: _ignored2, ...base } = raw; const account = resolveAccountConfig(cfg, accountId) ?? {}; return { ...base, ...account } as GoogleChatAccountConfig; } function parseServiceAccount(value: unknown): Record | null { if (value && typeof value === "object") { if (isSecretRef(value)) { return null; } return value as Record; } if (typeof value !== "string") { return null; } const trimmed = value.trim(); if (!trimmed) { return null; } try { return JSON.parse(trimmed) as Record; } catch { return null; } } function resolveCredentialsFromConfig(params: { accountId: string; account: GoogleChatAccountConfig; }): { credentials?: Record; credentialsFile?: string; source: GoogleChatCredentialSource; } { const { account, accountId } = params; const inline = parseServiceAccount(account.serviceAccount); if (inline) { return { credentials: inline, source: "inline" }; } if (isSecretRef(account.serviceAccount)) { throw new Error( `channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccount.source}:${account.serviceAccount.provider}:${account.serviceAccount.id}". Resolve this command against an active gateway runtime snapshot before reading it.`, ); } if (isSecretRef(account.serviceAccountRef)) { throw new Error( `channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccountRef.source}:${account.serviceAccountRef.provider}:${account.serviceAccountRef.id}". Resolve this command against an active gateway runtime snapshot before reading it.`, ); } const file = account.serviceAccountFile?.trim(); if (file) { return { credentialsFile: file, source: "file" }; } if (accountId === DEFAULT_ACCOUNT_ID) { const envJson = process.env[ENV_SERVICE_ACCOUNT]; const envInline = parseServiceAccount(envJson); if (envInline) { return { credentials: envInline, source: "env" }; } const envFile = process.env[ENV_SERVICE_ACCOUNT_FILE]?.trim(); if (envFile) { return { credentialsFile: envFile, source: "env" }; } } return { source: "none" }; } export function resolveGoogleChatAccount(params: { cfg: BotConfig; accountId?: string | null; }): ResolvedGoogleChatAccount { const accountId = normalizeAccountId(params.accountId); const baseEnabled = params.cfg.channels?.["googlechat"]?.enabled !== false; const merged = mergeGoogleChatAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const credentials = resolveCredentialsFromConfig({ accountId, account: merged }); return { accountId, name: merged.name?.trim() || undefined, enabled, config: merged, credentialSource: credentials.source, credentials: credentials.credentials, credentialsFile: credentials.credentialsFile, }; } export function listEnabledGoogleChatAccounts(cfg: BotConfig): ResolvedGoogleChatAccount[] { return listGoogleChatAccountIds(cfg) .map((accountId) => resolveGoogleChatAccount({ cfg, accountId })) .filter((account) => account.enabled); }