Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-discord-dm-partials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chat-adapter/discord": patch
---

Add `Partials.Channel` to gateway client for DM support
105 changes: 105 additions & 0 deletions packages/adapter-discord/src/gateway.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Tests for Discord Gateway client configuration.
*
* Verifies that the gateway Client is constructed with the correct
* intents and partials for receiving DM events.
*/

import { GatewayIntentBits, Partials } from "discord.js";
import { describe, expect, it, vi } from "vitest";
import { createDiscordAdapter } from "./index";

const { MockClient, mockClientInstance } = vi.hoisted(() => {
const mockClientInstance = {
on: vi.fn(),
login: vi.fn().mockResolvedValue("token"),
destroy: vi.fn(),
user: { username: "testbot", id: "bot123" },
};
const MockClient = vi.fn().mockImplementation(function (
this: typeof mockClientInstance
) {
Object.assign(this, mockClientInstance);
return this;
});
return { MockClient, mockClientInstance };
});

vi.mock("discord.js", async () => {
const actual = await vi.importActual("discord.js");
return {
...actual,
Client: MockClient,
};
});

const mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
child: vi.fn().mockReturnThis(),
};

const mockChat = {
getLogger: vi.fn().mockReturnValue(mockLogger),
getState: vi.fn(),
getUserName: vi.fn().mockReturnValue("bot"),
handleIncomingMessage: vi.fn(),
processAction: vi.fn(),
processAppHomeOpened: vi.fn(),
processAssistantContextChanged: vi.fn(),
processAssistantThreadStarted: vi.fn(),
processMessage: vi.fn(),
processModalClose: vi.fn(),
processModalSubmit: vi.fn(),
processReaction: vi.fn(),
};

describe("Gateway client configuration", () => {
it("includes Partials.Channel for DM support", async () => {
MockClient.mockClear();
mockClientInstance.on.mockClear();
mockClientInstance.login.mockClear();
mockClientInstance.destroy.mockClear();

const adapter = createDiscordAdapter({
botToken: "test-token",
publicKey: "a".repeat(64),
applicationId: "test-app-id",
logger: mockLogger,
});

await adapter.initialize(mockChat as never);

// Use a pre-aborted signal so the listener resolves immediately
const controller = new AbortController();
controller.abort();

let listenerPromise: Promise<unknown> | undefined;
const response = await adapter.startGatewayListener(
{
waitUntil: (p) => {
listenerPromise = p as Promise<unknown>;
},
},
1000,
controller.signal
);

expect(response.status).toBe(200);

// Wait for the background listener to finish
await listenerPromise;

expect(MockClient).toHaveBeenCalledOnce();

const clientOptions = MockClient.mock.calls[0][0] as {
intents: number[];
partials: number[];
};

expect(clientOptions.partials).toContain(Partials.Channel);
expect(clientOptions.intents).toContain(GatewayIntentBits.DirectMessages);
});
});
2 changes: 2 additions & 0 deletions packages/adapter-discord/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
type Message as DiscordJsMessage,
Events,
GatewayIntentBits,
Partials,
} from "discord.js";
import {
type APIEmbed,
Expand Down Expand Up @@ -1295,6 +1296,7 @@ export class DiscordAdapter implements Adapter<DiscordThreadId, unknown> {
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.DirectMessageReactions,
],
partials: [Partials.Channel],
});

let isShuttingDown = false;
Expand Down
Loading