Skip to content

Commit 5f22b68

Browse files
committed
feat: add session origin metadata helpers
1 parent 34590d2 commit 5f22b68

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { deriveSessionMetaPatch } from "./metadata.js";
4+
5+
describe("deriveSessionMetaPatch", () => {
6+
it("captures origin + group metadata", () => {
7+
const patch = deriveSessionMetaPatch({
8+
ctx: {
9+
Provider: "whatsapp",
10+
ChatType: "group",
11+
GroupSubject: "Family",
12+
From: "123@g.us",
13+
},
14+
sessionKey: "agent:main:whatsapp:group:123@g.us",
15+
});
16+
17+
expect(patch?.origin?.label).toBe("Family id:123@g.us");
18+
expect(patch?.origin?.provider).toBe("whatsapp");
19+
expect(patch?.subject).toBe("Family");
20+
expect(patch?.channel).toBe("whatsapp");
21+
expect(patch?.groupId).toBe("123@g.us");
22+
});
23+
});

src/config/sessions/metadata.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { MsgContext } from "../../auto-reply/templating.js";
2+
import { normalizeChatType } from "../../channels/chat-type.js";
3+
import { resolveConversationLabel } from "../../channels/conversation-label.js";
4+
import { getChannelDock } from "../../channels/dock.js";
5+
import { normalizeChannelId } from "../../channels/plugins/index.js";
6+
import { normalizeMessageChannel } from "../../utils/message-channel.js";
7+
import { buildGroupDisplayName, resolveGroupSessionKey } from "./group.js";
8+
import type { GroupKeyResolution, SessionEntry, SessionOrigin } from "./types.js";
9+
10+
const mergeOrigin = (
11+
existing: SessionOrigin | undefined,
12+
next: SessionOrigin | undefined,
13+
): SessionOrigin | undefined => {
14+
if (!existing && !next) return undefined;
15+
const merged: SessionOrigin = existing ? { ...existing } : {};
16+
if (next?.label) merged.label = next.label;
17+
if (next?.provider) merged.provider = next.provider;
18+
if (next?.surface) merged.surface = next.surface;
19+
if (next?.chatType) merged.chatType = next.chatType;
20+
if (next?.from) merged.from = next.from;
21+
if (next?.to) merged.to = next.to;
22+
if (next?.accountId) merged.accountId = next.accountId;
23+
if (next?.threadId != null && next.threadId !== "") merged.threadId = next.threadId;
24+
return Object.keys(merged).length > 0 ? merged : undefined;
25+
};
26+
27+
export function deriveSessionOrigin(ctx: MsgContext): SessionOrigin | undefined {
28+
const label = resolveConversationLabel(ctx)?.trim();
29+
const providerRaw =
30+
(typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel) ||
31+
ctx.Surface ||
32+
ctx.Provider;
33+
const provider = normalizeMessageChannel(providerRaw);
34+
const surface = ctx.Surface?.trim().toLowerCase();
35+
const chatType = normalizeChatType(ctx.ChatType) ?? undefined;
36+
const from = ctx.From?.trim();
37+
const to =
38+
(typeof ctx.OriginatingTo === "string" ? ctx.OriginatingTo : ctx.To)?.trim() ?? undefined;
39+
const accountId = ctx.AccountId?.trim();
40+
const threadId = ctx.MessageThreadId ?? undefined;
41+
42+
const origin: SessionOrigin = {};
43+
if (label) origin.label = label;
44+
if (provider) origin.provider = provider;
45+
if (surface) origin.surface = surface;
46+
if (chatType) origin.chatType = chatType;
47+
if (from) origin.from = from;
48+
if (to) origin.to = to;
49+
if (accountId) origin.accountId = accountId;
50+
if (threadId != null && threadId !== "") origin.threadId = threadId;
51+
52+
return Object.keys(origin).length > 0 ? origin : undefined;
53+
}
54+
55+
export function snapshotSessionOrigin(entry?: SessionEntry): SessionOrigin | undefined {
56+
if (!entry?.origin) return undefined;
57+
return { ...entry.origin };
58+
}
59+
60+
export function deriveGroupSessionPatch(params: {
61+
ctx: MsgContext;
62+
sessionKey: string;
63+
existing?: SessionEntry;
64+
groupResolution?: GroupKeyResolution | null;
65+
}): Partial<SessionEntry> | null {
66+
const resolution = params.groupResolution ?? resolveGroupSessionKey(params.ctx);
67+
if (!resolution?.channel) return null;
68+
69+
const channel = resolution.channel;
70+
const subject = params.ctx.GroupSubject?.trim();
71+
const space = params.ctx.GroupSpace?.trim();
72+
const explicitChannel = params.ctx.GroupChannel?.trim();
73+
const normalizedChannel = normalizeChannelId(channel);
74+
const isChannelProvider = Boolean(
75+
normalizedChannel &&
76+
getChannelDock(normalizedChannel)?.capabilities.chatTypes.includes("channel"),
77+
);
78+
const nextGroupChannel =
79+
explicitChannel ??
80+
((resolution.chatType === "channel" || isChannelProvider) &&
81+
subject &&
82+
subject.startsWith("#")
83+
? subject
84+
: undefined);
85+
const nextSubject = nextGroupChannel ? undefined : subject;
86+
87+
const patch: Partial<SessionEntry> = {
88+
chatType: resolution.chatType ?? "group",
89+
channel,
90+
groupId: resolution.id,
91+
};
92+
if (nextSubject) patch.subject = nextSubject;
93+
if (nextGroupChannel) patch.groupChannel = nextGroupChannel;
94+
if (space) patch.space = space;
95+
96+
const displayName = buildGroupDisplayName({
97+
provider: channel,
98+
subject: nextSubject ?? params.existing?.subject,
99+
groupChannel: nextGroupChannel ?? params.existing?.groupChannel,
100+
space: space ?? params.existing?.space,
101+
id: resolution.id,
102+
key: params.sessionKey,
103+
});
104+
if (displayName) patch.displayName = displayName;
105+
106+
return patch;
107+
}
108+
109+
export function deriveSessionMetaPatch(params: {
110+
ctx: MsgContext;
111+
sessionKey: string;
112+
existing?: SessionEntry;
113+
groupResolution?: GroupKeyResolution | null;
114+
}): Partial<SessionEntry> | null {
115+
const groupPatch = deriveGroupSessionPatch(params);
116+
const origin = deriveSessionOrigin(params.ctx);
117+
if (!groupPatch && !origin) return null;
118+
119+
const patch: Partial<SessionEntry> = groupPatch ? { ...groupPatch } : {};
120+
const mergedOrigin = mergeOrigin(params.existing?.origin, origin);
121+
if (mergedOrigin) patch.origin = mergedOrigin;
122+
123+
return Object.keys(patch).length > 0 ? patch : null;
124+
}

0 commit comments

Comments
 (0)