import type { ClawdbotConfig, PluginRuntime } from "clawdbot/plugin-sdk"; import type { MSTeamsConversationStore, StoredConversationReference, } from "./conversation-store.js"; import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js"; import type { MSTeamsAdapter } from "./messenger.js"; import { createMSTeamsAdapter, loadMSTeamsSdkWithAuth } from "./sdk.js"; import { resolveMSTeamsCredentials } from "./token.js"; type GetChildLogger = PluginRuntime["logging"]["getChildLogger"]; let _log: ReturnType | undefined; const getLog = async (): Promise> => { if (_log) return _log; const { getChildLogger } = await import("../logging.js"); _log = getChildLogger({ name: "msteams:send" }); return _log; }; export type MSTeamsProactiveContext = { appId: string; conversationId: string; ref: StoredConversationReference; adapter: MSTeamsAdapter; log: Awaited>; }; /** * Parse the target value into a conversation reference lookup key. * Supported formats: * - conversation:19:abc@thread.tacv2 → lookup by conversation ID * - user:aad-object-id → lookup by user AAD object ID * - 19:abc@thread.tacv2 → direct conversation ID */ function parseRecipient(to: string): { type: "conversation" | "user"; id: string; } { const trimmed = to.trim(); const finalize = (type: "conversation" | "user", id: string) => { const normalized = id.trim(); if (!normalized) { throw new Error(`Invalid target value: missing ${type} id`); } return { type, id: normalized }; }; if (trimmed.startsWith("conversation:")) { return finalize("conversation", trimmed.slice("conversation:".length)); } if (trimmed.startsWith("user:")) { return finalize("user", trimmed.slice("user:".length)); } // Assume it's a conversation ID if it looks like one if (trimmed.startsWith("19:") || trimmed.includes("@thread")) { return finalize("conversation", trimmed); } // Otherwise treat as user ID return finalize("user", trimmed); } /** * Find a stored conversation reference for the given recipient. */ async function findConversationReference(recipient: { type: "conversation" | "user"; id: string; store: MSTeamsConversationStore; }): Promise<{ conversationId: string; ref: StoredConversationReference; } | null> { if (recipient.type === "conversation") { const ref = await recipient.store.get(recipient.id); if (ref) return { conversationId: recipient.id, ref }; return null; } const found = await recipient.store.findByUserId(recipient.id); if (!found) return null; return { conversationId: found.conversationId, ref: found.reference }; } export async function resolveMSTeamsSendContext(params: { cfg: ClawdbotConfig; to: string; }): Promise { const msteamsCfg = params.cfg.channels?.msteams; if (!msteamsCfg?.enabled) { throw new Error("msteams provider is not enabled"); } const creds = resolveMSTeamsCredentials(msteamsCfg); if (!creds) { throw new Error("msteams credentials not configured"); } const store = createMSTeamsConversationStoreFs(); // Parse recipient and find conversation reference const recipient = parseRecipient(params.to); const found = await findConversationReference({ ...recipient, store }); if (!found) { throw new Error( `No conversation reference found for ${recipient.type}:${recipient.id}. ` + `The bot must receive a message from this conversation before it can send proactively.`, ); } const { conversationId, ref } = found; const log = await getLog(); const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds); const adapter = createMSTeamsAdapter(authConfig, sdk); return { appId: creds.appId, conversationId, ref, adapter: adapter as unknown as MSTeamsAdapter, log, }; }