Files
openclaw/extensions/googlechat/src/targets.ts
Peter Steinberger b50822aab5 refactor(channels): remove Google Chat and Matrix test exports (#107951)
* refactor(googlechat): privatize internal test seams

* refactor(matrix): remove internal test exports

* chore(deadcode): refresh export baseline
2026-07-14 20:06:39 -07:00

131 lines
4.3 KiB
TypeScript

import { buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/channel-core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
// Googlechat plugin module implements targets behavior.
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import { resolveGoogleChatAccount, type ResolvedGoogleChatAccount } from "./accounts.js";
import { findGoogleChatDirectMessage, getGoogleChatSpace } from "./api.js";
import type { GoogleChatSpace } from "./types.js";
export function normalizeGoogleChatTarget(raw?: string | null): string | undefined {
const trimmed = raw?.trim();
if (!trimmed) {
return undefined;
}
const withoutPrefix = trimmed.replace(/^(googlechat|google-chat|gchat):/i, "");
const normalized = withoutPrefix
.replace(/^user:(users\/)?/i, "users/")
.replace(/^space:(spaces\/)?/i, "spaces/");
if (isGoogleChatUserTarget(normalized)) {
const suffix = normalized.slice("users/".length);
return suffix.includes("@") ? `users/${normalizeLowercaseStringOrEmpty(suffix)}` : normalized;
}
if (isGoogleChatSpaceTarget(normalized)) {
return normalized;
}
if (normalized.includes("@")) {
return `users/${normalizeLowercaseStringOrEmpty(normalized)}`;
}
return normalized;
}
export function isGoogleChatUserTarget(value: string): boolean {
return normalizeLowercaseStringOrEmpty(value).startsWith("users/");
}
export function isGoogleChatSpaceTarget(value: string): boolean {
return normalizeLowercaseStringOrEmpty(value).startsWith("spaces/");
}
function resolveGoogleChatSpaceChatType(space: GoogleChatSpace): "direct" | "group" | undefined {
const spaceType = (space.spaceType ?? "").toUpperCase();
// The current field wins when both current and deprecated fields are present.
if (spaceType === "DIRECT_MESSAGE") {
return "direct";
}
if (spaceType === "SPACE" || spaceType === "GROUP_CHAT") {
return "group";
}
if (space.singleUserBotDm === true || (space.type ?? "").toUpperCase() === "DM") {
return "direct";
}
if ((space.type ?? "").toUpperCase() === "ROOM") {
return "group";
}
return undefined;
}
export function isGoogleChatGroupSpace(space: GoogleChatSpace): boolean {
// Legacy webhook payloads can omit type metadata. Preserve their historical
// group default while outbound routing requires an exact API classification.
return resolveGoogleChatSpaceChatType(space) !== "direct";
}
function stripMessageSuffix(target: string): string {
const index = target.indexOf("/messages/");
if (index === -1) {
return target;
}
return target.slice(0, index);
}
export async function resolveGoogleChatOutboundSpace(params: {
account: ResolvedGoogleChatAccount;
target: string;
}): Promise<string> {
const normalized = normalizeGoogleChatTarget(params.target);
if (!normalized) {
throw new Error("Missing Google Chat target.");
}
const base = stripMessageSuffix(normalized);
if (isGoogleChatSpaceTarget(base)) {
return base;
}
if (isGoogleChatUserTarget(base)) {
const dm = await findGoogleChatDirectMessage({
account: params.account,
userName: base,
});
if (!dm?.name) {
throw new Error(`No Google Chat DM found for ${base}`);
}
return dm.name;
}
return base;
}
export async function resolveGoogleChatOutboundSessionRoute(params: {
cfg: OpenClawConfig;
agentId: string;
accountId?: string | null;
target: string;
}) {
const account = resolveGoogleChatAccount({ cfg: params.cfg, accountId: params.accountId });
const spaceName = await resolveGoogleChatOutboundSpace({ account, target: params.target });
if (!isGoogleChatSpaceTarget(spaceName)) {
return null;
}
let space: GoogleChatSpace;
try {
space = await getGoogleChatSpace({ account, spaceName });
} catch {
// Space classification only enriches session routing. Delivery must remain
// available when this auxiliary read is unavailable or lacks permission.
return null;
}
const chatType = resolveGoogleChatSpaceChatType(space);
if (!chatType) {
return null;
}
return buildChannelOutboundSessionRoute({
cfg: params.cfg,
agentId: params.agentId,
channel: "googlechat",
accountId: params.accountId,
recipientSessionExact: true,
peer: { kind: chatType, id: spaceName },
chatType,
from: `googlechat:${spaceName}`,
to: spaceName,
});
}