From ad7399b6e60512d7fb1fd6b27dead5dcd2918478 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 01:17:06 +0000 Subject: [PATCH] refactor(sessions): add provider key normalizers --- ...explicit-session-key-normalization.test.ts | 53 +++++++++++++++++++ .../explicit-session-key-normalization.ts | 35 ++++++++++++ src/config/sessions/session-key.ts | 4 +- src/discord/session-key-normalization.test.ts | 10 ++++ src/discord/session-key-normalization.ts | 1 + 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/config/sessions/explicit-session-key-normalization.test.ts create mode 100644 src/config/sessions/explicit-session-key-normalization.ts diff --git a/src/config/sessions/explicit-session-key-normalization.test.ts b/src/config/sessions/explicit-session-key-normalization.test.ts new file mode 100644 index 00000000000..8079b140e1e --- /dev/null +++ b/src/config/sessions/explicit-session-key-normalization.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; +import type { MsgContext } from "../../auto-reply/templating.js"; +import { normalizeExplicitSessionKey } from "./explicit-session-key-normalization.js"; + +function makeCtx(overrides: Partial): MsgContext { + return { + Body: "", + From: "", + To: "", + ...overrides, + } as MsgContext; +} + +describe("normalizeExplicitSessionKey", () => { + it("dispatches discord keys through the provider normalizer", () => { + expect( + normalizeExplicitSessionKey( + "agent:fina:discord:channel:123456", + makeCtx({ + Surface: "discord", + ChatType: "direct", + From: "discord:123456", + SenderId: "123456", + }), + ), + ).toBe("agent:fina:discord:direct:123456"); + }); + + it("infers the provider from From when explicit provider fields are absent", () => { + expect( + normalizeExplicitSessionKey( + "discord:dm:123456", + makeCtx({ + ChatType: "direct", + From: "discord:123456", + SenderId: "123456", + }), + ), + ).toBe("discord:direct:123456"); + }); + + it("lowercases and passes through unknown providers unchanged", () => { + expect( + normalizeExplicitSessionKey( + "Agent:Fina:Slack:DM:ABC", + makeCtx({ + Surface: "slack", + From: "slack:U123", + }), + ), + ).toBe("agent:fina:slack:dm:abc"); + }); +}); diff --git a/src/config/sessions/explicit-session-key-normalization.ts b/src/config/sessions/explicit-session-key-normalization.ts new file mode 100644 index 00000000000..5f2b5c61159 --- /dev/null +++ b/src/config/sessions/explicit-session-key-normalization.ts @@ -0,0 +1,35 @@ +import type { MsgContext } from "../../auto-reply/templating.js"; +import { normalizeExplicitDiscordSessionKey } from "../../discord/session-key-normalization.js"; + +type ExplicitSessionKeyNormalizer = (sessionKey: string, ctx: MsgContext) => string; + +const EXPLICIT_SESSION_KEY_NORMALIZERS: Record = { + discord: normalizeExplicitDiscordSessionKey, +}; + +function resolveExplicitSessionKeyProvider( + sessionKey: string, + ctx: Pick, +): string | undefined { + const explicitProvider = [ctx.Surface, ctx.Provider] + .map((entry) => entry?.trim().toLowerCase()) + .find((entry) => entry && entry in EXPLICIT_SESSION_KEY_NORMALIZERS); + if (explicitProvider) { + return explicitProvider; + } + + const from = (ctx.From ?? "").trim().toLowerCase(); + if (from.startsWith("discord:")) { + return "discord"; + } + if (sessionKey.startsWith("discord:") || sessionKey.includes(":discord:")) { + return "discord"; + } + return undefined; +} + +export function normalizeExplicitSessionKey(sessionKey: string, ctx: MsgContext): string { + const normalized = sessionKey.trim().toLowerCase(); + const provider = resolveExplicitSessionKeyProvider(normalized, ctx); + return provider ? EXPLICIT_SESSION_KEY_NORMALIZERS[provider](normalized, ctx) : normalized; +} diff --git a/src/config/sessions/session-key.ts b/src/config/sessions/session-key.ts index 1443be30630..37b47276920 100644 --- a/src/config/sessions/session-key.ts +++ b/src/config/sessions/session-key.ts @@ -1,11 +1,11 @@ import type { MsgContext } from "../../auto-reply/templating.js"; -import { normalizeExplicitDiscordSessionKey } from "../../discord/session-key-normalization.js"; import { buildAgentMainSessionKey, DEFAULT_AGENT_ID, normalizeMainKey, } from "../../routing/session-key.js"; import { normalizeE164 } from "../../utils.js"; +import { normalizeExplicitSessionKey } from "./explicit-session-key-normalization.js"; import { resolveGroupSessionKey } from "./group.js"; import type { SessionScope } from "./types.js"; @@ -29,7 +29,7 @@ export function deriveSessionKey(scope: SessionScope, ctx: MsgContext) { export function resolveSessionKey(scope: SessionScope, ctx: MsgContext, mainKey?: string) { const explicit = ctx.SessionKey?.trim(); if (explicit) { - return normalizeExplicitDiscordSessionKey(explicit, ctx); + return normalizeExplicitSessionKey(explicit, ctx); } const raw = deriveSessionKey(scope, ctx); if (scope === "global") { diff --git a/src/discord/session-key-normalization.test.ts b/src/discord/session-key-normalization.test.ts index 22066c3da95..1e24440b7aa 100644 --- a/src/discord/session-key-normalization.test.ts +++ b/src/discord/session-key-normalization.test.ts @@ -2,6 +2,16 @@ import { describe, expect, it } from "vitest"; import { normalizeExplicitDiscordSessionKey } from "./session-key-normalization.js"; describe("normalizeExplicitDiscordSessionKey", () => { + it("rewrites bare discord:dm keys for direct chats", () => { + expect( + normalizeExplicitDiscordSessionKey("discord:dm:123456", { + ChatType: "direct", + From: "discord:123456", + SenderId: "123456", + }), + ).toBe("discord:direct:123456"); + }); + it("rewrites legacy discord:dm keys for direct chats", () => { expect( normalizeExplicitDiscordSessionKey("agent:fina:discord:dm:123456", { diff --git a/src/discord/session-key-normalization.ts b/src/discord/session-key-normalization.ts index 05a48c7e06b..67d267aac21 100644 --- a/src/discord/session-key-normalization.ts +++ b/src/discord/session-key-normalization.ts @@ -10,6 +10,7 @@ export function normalizeExplicitDiscordSessionKey( return normalized; } + normalized = normalized.replace(/^(discord:)dm:/, "$1direct:"); normalized = normalized.replace(/^(agent:[^:]+:discord:)dm:/, "$1direct:"); const match = normalized.match(/^((?:agent:[^:]+:)?)discord:channel:([^:]+)$/); if (!match) {