File size: 3,639 Bytes
fc93158 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | export type ParsedAgentSessionKey = {
agentId: string;
rest: string;
};
export type SessionKeyChatType = "direct" | "group" | "channel" | "unknown";
/**
* Parse agent-scoped session keys in a canonical, case-insensitive way.
* Returned values are normalized to lowercase for stable comparisons/routing.
*/
export function parseAgentSessionKey(
sessionKey: string | undefined | null,
): ParsedAgentSessionKey | null {
const raw = (sessionKey ?? "").trim().toLowerCase();
if (!raw) {
return null;
}
const parts = raw.split(":").filter(Boolean);
if (parts.length < 3) {
return null;
}
if (parts[0] !== "agent") {
return null;
}
const agentId = parts[1]?.trim();
const rest = parts.slice(2).join(":");
if (!agentId || !rest) {
return null;
}
return { agentId, rest };
}
/**
* Best-effort chat-type extraction from session keys across canonical and legacy formats.
*/
export function deriveSessionChatType(sessionKey: string | undefined | null): SessionKeyChatType {
const raw = (sessionKey ?? "").trim().toLowerCase();
if (!raw) {
return "unknown";
}
const scoped = parseAgentSessionKey(raw)?.rest ?? raw;
const tokens = new Set(scoped.split(":").filter(Boolean));
if (tokens.has("group")) {
return "group";
}
if (tokens.has("channel")) {
return "channel";
}
if (tokens.has("direct") || tokens.has("dm")) {
return "direct";
}
// Legacy Discord keys can be shaped like:
// discord:<accountId>:guild-<guildId>:channel-<channelId>
if (/^discord:(?:[^:]+:)?guild-[^:]+:channel-[^:]+$/.test(scoped)) {
return "channel";
}
return "unknown";
}
export function isCronRunSessionKey(sessionKey: string | undefined | null): boolean {
const parsed = parseAgentSessionKey(sessionKey);
if (!parsed) {
return false;
}
return /^cron:[^:]+:run:[^:]+$/.test(parsed.rest);
}
export function isCronSessionKey(sessionKey: string | undefined | null): boolean {
const parsed = parseAgentSessionKey(sessionKey);
if (!parsed) {
return false;
}
return parsed.rest.toLowerCase().startsWith("cron:");
}
export function isSubagentSessionKey(sessionKey: string | undefined | null): boolean {
const raw = (sessionKey ?? "").trim();
if (!raw) {
return false;
}
if (raw.toLowerCase().startsWith("subagent:")) {
return true;
}
const parsed = parseAgentSessionKey(raw);
return Boolean((parsed?.rest ?? "").toLowerCase().startsWith("subagent:"));
}
export function getSubagentDepth(sessionKey: string | undefined | null): number {
const raw = (sessionKey ?? "").trim().toLowerCase();
if (!raw) {
return 0;
}
return raw.split(":subagent:").length - 1;
}
export function isAcpSessionKey(sessionKey: string | undefined | null): boolean {
const raw = (sessionKey ?? "").trim();
if (!raw) {
return false;
}
const normalized = raw.toLowerCase();
if (normalized.startsWith("acp:")) {
return true;
}
const parsed = parseAgentSessionKey(raw);
return Boolean((parsed?.rest ?? "").toLowerCase().startsWith("acp:"));
}
const THREAD_SESSION_MARKERS = [":thread:", ":topic:"];
export function resolveThreadParentSessionKey(
sessionKey: string | undefined | null,
): string | null {
const raw = (sessionKey ?? "").trim();
if (!raw) {
return null;
}
const normalized = raw.toLowerCase();
let idx = -1;
for (const marker of THREAD_SESSION_MARKERS) {
const candidate = normalized.lastIndexOf(marker);
if (candidate > idx) {
idx = candidate;
}
}
if (idx <= 0) {
return null;
}
const parent = raw.slice(0, idx).trim();
return parent ? parent : null;
}
|