mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 12:41:42 +00:00
195 lines
6.1 KiB
TypeScript
195 lines
6.1 KiB
TypeScript
// Telegram plugin module implements conversation route behavior.
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import {
|
|
resolveConfiguredBindingRoute,
|
|
resolveRuntimeConversationBindingRoute,
|
|
type ConfiguredBindingRouteResult,
|
|
} from "openclaw/plugin-sdk/conversation-runtime";
|
|
import {
|
|
buildAgentSessionKey,
|
|
deriveLastRoutePolicy,
|
|
normalizeAccountId,
|
|
resolveAgentRoute,
|
|
} from "openclaw/plugin-sdk/routing";
|
|
import { buildAgentMainSessionKey, sanitizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import { resolveDefaultTelegramAccountId } from "./accounts.js";
|
|
import {
|
|
buildTelegramGroupPeerId,
|
|
buildTelegramParentPeer,
|
|
resolveTelegramDirectPeerId,
|
|
} from "./bot/helpers.js";
|
|
|
|
type TelegramResolvedRoute = ReturnType<typeof resolveAgentRoute>;
|
|
type ConfiguredTelegramBinding = NonNullable<ConfiguredBindingRouteResult["bindingResolution"]>;
|
|
|
|
export type TelegramConversationBindingMode =
|
|
| { kind: "none" }
|
|
| {
|
|
kind: "configured";
|
|
binding: ConfiguredTelegramBinding;
|
|
sessionKey: string;
|
|
}
|
|
| {
|
|
kind: "runtime-bound";
|
|
sessionKey: string;
|
|
}
|
|
| { kind: "plugin-owned-runtime" };
|
|
|
|
type TelegramConversationRouteResult = {
|
|
route: TelegramResolvedRoute;
|
|
bindingMode: TelegramConversationBindingMode;
|
|
};
|
|
|
|
export function resolveTelegramConversationRoute(params: {
|
|
cfg: OpenClawConfig;
|
|
accountId: string;
|
|
chatId: number | string;
|
|
isGroup: boolean;
|
|
resolvedThreadId?: number;
|
|
replyThreadId?: number;
|
|
senderId?: string | number | null;
|
|
topicAgentId?: string | null;
|
|
}): TelegramConversationRouteResult {
|
|
const peerId = params.isGroup
|
|
? buildTelegramGroupPeerId(params.chatId, params.resolvedThreadId)
|
|
: resolveTelegramDirectPeerId({
|
|
chatId: params.chatId,
|
|
senderId: params.senderId,
|
|
});
|
|
const parentPeer = buildTelegramParentPeer({
|
|
isGroup: params.isGroup,
|
|
resolvedThreadId: params.resolvedThreadId,
|
|
chatId: params.chatId,
|
|
});
|
|
let route = resolveAgentRoute({
|
|
cfg: params.cfg,
|
|
channel: "telegram",
|
|
accountId: params.accountId,
|
|
peer: {
|
|
kind: params.isGroup ? "group" : "direct",
|
|
id: peerId,
|
|
},
|
|
parentPeer,
|
|
});
|
|
|
|
const rawTopicAgentId = params.topicAgentId?.trim();
|
|
if (rawTopicAgentId) {
|
|
// Preserve the configured topic agent ID so topic-bound sessions stay stable
|
|
// even when that agent is not present in the current config snapshot.
|
|
const topicAgentId = sanitizeAgentId(rawTopicAgentId);
|
|
const sessionKey = normalizeLowercaseStringOrEmpty(
|
|
buildAgentSessionKey({
|
|
agentId: topicAgentId,
|
|
channel: "telegram",
|
|
accountId: params.accountId,
|
|
peer: { kind: params.isGroup ? "group" : "direct", id: peerId },
|
|
dmScope: params.cfg.session?.dmScope,
|
|
identityLinks: params.cfg.session?.identityLinks,
|
|
}),
|
|
);
|
|
const mainSessionKey = normalizeLowercaseStringOrEmpty(
|
|
buildAgentMainSessionKey({
|
|
agentId: topicAgentId,
|
|
}),
|
|
);
|
|
route = {
|
|
...route,
|
|
agentId: topicAgentId,
|
|
sessionKey,
|
|
mainSessionKey,
|
|
lastRoutePolicy: deriveLastRoutePolicy({
|
|
sessionKey,
|
|
mainSessionKey,
|
|
}),
|
|
};
|
|
logVerbose(
|
|
`telegram: topic route override: topic=${params.resolvedThreadId ?? params.replyThreadId} agent=${topicAgentId} sessionKey=${route.sessionKey}`,
|
|
);
|
|
}
|
|
|
|
const configuredRoute = resolveConfiguredBindingRoute({
|
|
cfg: params.cfg,
|
|
route,
|
|
conversation: {
|
|
channel: "telegram",
|
|
accountId: params.accountId,
|
|
conversationId: peerId,
|
|
parentConversationId: params.isGroup ? String(params.chatId) : undefined,
|
|
},
|
|
});
|
|
route = configuredRoute.route;
|
|
let bindingMode: TelegramConversationBindingMode = configuredRoute.bindingResolution
|
|
? {
|
|
kind: "configured",
|
|
binding: configuredRoute.bindingResolution,
|
|
sessionKey: configuredRoute.boundSessionKey ?? route.sessionKey,
|
|
}
|
|
: { kind: "none" };
|
|
|
|
const runtimeBindingConversationId =
|
|
params.replyThreadId != null
|
|
? `${params.chatId}:topic:${params.replyThreadId}`
|
|
: String(params.chatId);
|
|
const runtimeRoute = resolveRuntimeConversationBindingRoute({
|
|
route,
|
|
conversation: {
|
|
channel: "telegram",
|
|
accountId: params.accountId,
|
|
conversationId: runtimeBindingConversationId,
|
|
},
|
|
});
|
|
route = runtimeRoute.route;
|
|
if (runtimeRoute.bindingRecord) {
|
|
bindingMode = runtimeRoute.boundSessionKey
|
|
? { kind: "runtime-bound", sessionKey: runtimeRoute.boundSessionKey }
|
|
: { kind: "plugin-owned-runtime" };
|
|
logVerbose(
|
|
runtimeRoute.boundSessionKey
|
|
? `telegram: routed via bound conversation ${runtimeBindingConversationId} -> ${runtimeRoute.boundSessionKey}`
|
|
: `telegram: plugin-bound conversation ${runtimeBindingConversationId}`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
route,
|
|
bindingMode,
|
|
};
|
|
}
|
|
|
|
export function resolveTelegramConversationBaseSessionKey(params: {
|
|
cfg: OpenClawConfig;
|
|
route: Pick<
|
|
ReturnType<typeof resolveTelegramConversationRoute>["route"],
|
|
"agentId" | "accountId" | "matchedBy" | "sessionKey"
|
|
>;
|
|
chatId: number | string;
|
|
isGroup: boolean;
|
|
senderId?: string | number | null;
|
|
}): string {
|
|
const routeAccountId = normalizeAccountId(params.route.accountId);
|
|
const defaultAccountId = normalizeAccountId(resolveDefaultTelegramAccountId(params.cfg));
|
|
const isNamedAccountFallback =
|
|
routeAccountId !== defaultAccountId && params.route.matchedBy === "default";
|
|
if (!isNamedAccountFallback || params.isGroup) {
|
|
return params.route.sessionKey;
|
|
}
|
|
return normalizeLowercaseStringOrEmpty(
|
|
buildAgentSessionKey({
|
|
agentId: params.route.agentId,
|
|
channel: "telegram",
|
|
accountId: params.route.accountId,
|
|
peer: {
|
|
kind: "direct",
|
|
id: resolveTelegramDirectPeerId({
|
|
chatId: params.chatId,
|
|
senderId: params.senderId,
|
|
}),
|
|
},
|
|
dmScope: "per-account-channel-peer",
|
|
identityLinks: params.cfg.session?.identityLinks,
|
|
}),
|
|
);
|
|
}
|