refactor(discord): centralize autoThread reply plan (#856)
This commit is contained in:
@@ -25,9 +25,7 @@ import {
|
||||
import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js";
|
||||
import { deliverDiscordReply } from "./reply-delivery.js";
|
||||
import {
|
||||
maybeCreateDiscordAutoThread,
|
||||
resolveDiscordAutoThreadContext,
|
||||
resolveDiscordReplyDeliveryPlan,
|
||||
resolveDiscordAutoThreadReplyPlan,
|
||||
resolveDiscordThreadStarter,
|
||||
} from "./threading.js";
|
||||
import { sendTyping } from "./typing.js";
|
||||
@@ -201,35 +199,22 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
parentSessionKey,
|
||||
useSuffix: false,
|
||||
});
|
||||
const inboundTarget = `channel:${message.channelId}`;
|
||||
const createdThreadId = await maybeCreateDiscordAutoThread({
|
||||
const replyPlan = await resolveDiscordAutoThreadReplyPlan({
|
||||
client,
|
||||
message,
|
||||
isGuildMessage,
|
||||
channelConfig,
|
||||
threadChannel,
|
||||
threadChannel,
|
||||
baseText: baseText ?? "",
|
||||
combinedBody,
|
||||
});
|
||||
const replyPlan = resolveDiscordReplyDeliveryPlan({
|
||||
replyTarget: inboundTarget,
|
||||
replyToMode,
|
||||
messageId: message.id,
|
||||
threadChannel,
|
||||
createdThreadId,
|
||||
agentId: route.agentId,
|
||||
channel: route.channel,
|
||||
});
|
||||
const deliverTarget = replyPlan.deliverTarget;
|
||||
const replyTarget = replyPlan.replyTarget;
|
||||
const replyReference = replyPlan.replyReference;
|
||||
|
||||
const autoThreadContext = isGuildMessage
|
||||
? resolveDiscordAutoThreadContext({
|
||||
agentId: route.agentId,
|
||||
channel: route.channel,
|
||||
messageChannelId: message.channelId,
|
||||
createdThreadId,
|
||||
})
|
||||
: null;
|
||||
const autoThreadContext = replyPlan.autoThreadContext;
|
||||
|
||||
const effectiveFrom = isDirectMessage
|
||||
? `discord:${author.id}`
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
||||
import { resolveDiscordAutoThreadContext } from "./threading.js";
|
||||
import type { Client } from "@buape/carbon";
|
||||
import {
|
||||
resolveDiscordAutoThreadContext,
|
||||
resolveDiscordAutoThreadReplyPlan,
|
||||
resolveDiscordReplyDeliveryPlan,
|
||||
} from "./threading.js";
|
||||
|
||||
describe("resolveDiscordAutoThreadContext", () => {
|
||||
it("returns null when no createdThreadId", () => {
|
||||
@@ -42,3 +47,88 @@ describe("resolveDiscordAutoThreadContext", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiscordReplyDeliveryPlan", () => {
|
||||
it("uses reply references when posting to the original target", () => {
|
||||
const plan = resolveDiscordReplyDeliveryPlan({
|
||||
replyTarget: "channel:parent",
|
||||
replyToMode: "all",
|
||||
messageId: "m1",
|
||||
threadChannel: null,
|
||||
createdThreadId: null,
|
||||
});
|
||||
expect(plan.deliverTarget).toBe("channel:parent");
|
||||
expect(plan.replyTarget).toBe("channel:parent");
|
||||
expect(plan.replyReference.use()).toBe("m1");
|
||||
});
|
||||
|
||||
it("disables reply references when autoThread creates a new thread", () => {
|
||||
const plan = resolveDiscordReplyDeliveryPlan({
|
||||
replyTarget: "channel:parent",
|
||||
replyToMode: "all",
|
||||
messageId: "m1",
|
||||
threadChannel: null,
|
||||
createdThreadId: "thread",
|
||||
});
|
||||
expect(plan.deliverTarget).toBe("channel:thread");
|
||||
expect(plan.replyTarget).toBe("channel:thread");
|
||||
expect(plan.replyReference.use()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("always uses existingId when inside a thread", () => {
|
||||
const plan = resolveDiscordReplyDeliveryPlan({
|
||||
replyTarget: "channel:thread",
|
||||
replyToMode: "off",
|
||||
messageId: "m1",
|
||||
threadChannel: { id: "thread" },
|
||||
createdThreadId: null,
|
||||
});
|
||||
expect(plan.replyReference.use()).toBe("m1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiscordAutoThreadReplyPlan", () => {
|
||||
it("switches delivery + session context to the created thread", async () => {
|
||||
const client = {
|
||||
rest: { post: async () => ({ id: "thread" }) },
|
||||
} as unknown as Client;
|
||||
const plan = await resolveDiscordAutoThreadReplyPlan({
|
||||
client,
|
||||
message: { id: "m1", channelId: "parent" } as unknown as import("./listeners.js").DiscordMessageEvent["message"],
|
||||
isGuildMessage: true,
|
||||
channelConfig: { autoThread: true } as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
|
||||
threadChannel: null,
|
||||
baseText: "hello",
|
||||
combinedBody: "hello",
|
||||
replyToMode: "all",
|
||||
agentId: "agent",
|
||||
channel: "discord",
|
||||
});
|
||||
expect(plan.deliverTarget).toBe("channel:thread");
|
||||
expect(plan.replyReference.use()).toBeUndefined();
|
||||
expect(plan.autoThreadContext?.SessionKey).toBe(
|
||||
buildAgentSessionKey({
|
||||
agentId: "agent",
|
||||
channel: "discord",
|
||||
peer: { kind: "channel", id: "thread" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does nothing when autoThread is disabled", async () => {
|
||||
const client = { rest: { post: async () => ({ id: "thread" }) } } as unknown as Client;
|
||||
const plan = await resolveDiscordAutoThreadReplyPlan({
|
||||
client,
|
||||
message: { id: "m1", channelId: "parent" } as unknown as import("./listeners.js").DiscordMessageEvent["message"],
|
||||
isGuildMessage: true,
|
||||
channelConfig: { autoThread: false } as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
|
||||
threadChannel: null,
|
||||
baseText: "hello",
|
||||
combinedBody: "hello",
|
||||
replyToMode: "all",
|
||||
agentId: "agent",
|
||||
channel: "discord",
|
||||
});
|
||||
expect(plan.deliverTarget).toBe("channel:parent");
|
||||
expect(plan.autoThreadContext).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,6 +202,51 @@ export function resolveDiscordAutoThreadContext(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export type DiscordAutoThreadReplyPlan = DiscordReplyDeliveryPlan & {
|
||||
createdThreadId?: string;
|
||||
autoThreadContext: DiscordAutoThreadContext | null;
|
||||
};
|
||||
|
||||
export async function resolveDiscordAutoThreadReplyPlan(params: {
|
||||
client: Client;
|
||||
message: DiscordMessageEvent["message"];
|
||||
isGuildMessage: boolean;
|
||||
channelConfig?: DiscordChannelConfigResolved | null;
|
||||
threadChannel?: DiscordThreadChannel | null;
|
||||
baseText: string;
|
||||
combinedBody: string;
|
||||
replyToMode: ReplyToMode;
|
||||
agentId: string;
|
||||
channel: string;
|
||||
}): Promise<DiscordAutoThreadReplyPlan> {
|
||||
const originalReplyTarget = `channel:${params.message.channelId}`;
|
||||
const createdThreadId = await maybeCreateDiscordAutoThread({
|
||||
client: params.client,
|
||||
message: params.message,
|
||||
isGuildMessage: params.isGuildMessage,
|
||||
channelConfig: params.channelConfig,
|
||||
threadChannel: params.threadChannel,
|
||||
baseText: params.baseText,
|
||||
combinedBody: params.combinedBody,
|
||||
});
|
||||
const deliveryPlan = resolveDiscordReplyDeliveryPlan({
|
||||
replyTarget: originalReplyTarget,
|
||||
replyToMode: params.replyToMode,
|
||||
messageId: params.message.id,
|
||||
threadChannel: params.threadChannel,
|
||||
createdThreadId,
|
||||
});
|
||||
const autoThreadContext = params.isGuildMessage
|
||||
? resolveDiscordAutoThreadContext({
|
||||
agentId: params.agentId,
|
||||
channel: params.channel,
|
||||
messageChannelId: params.message.channelId,
|
||||
createdThreadId,
|
||||
})
|
||||
: null;
|
||||
return { ...deliveryPlan, createdThreadId, autoThreadContext };
|
||||
}
|
||||
|
||||
export async function maybeCreateDiscordAutoThread(params: {
|
||||
client: Client;
|
||||
message: DiscordMessageEvent["message"];
|
||||
|
||||
Reference in New Issue
Block a user