Skip to content

Commit 5ca4293

Browse files
CTO AgentPaperclip-Paperclip
andcommitted
fix: lazy bot init and thread ID validation (review feedback)
- bot.ts: Replace eager module-scope singleton with lazy getContentAgentBot() so Vercel build does not crash when content-agent env vars are not yet configured - getThread.ts: Add regex validation for adapter:channel:thread format, throw descriptive error on malformed IDs - registerHandlers.ts: Convert side-effect import to explicit ensureHandlersRegistered() call with idempotency guard - Route files updated to use getContentAgentBot() and ensureHandlersRegistered() Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent 7053180 commit 5ca4293

File tree

5 files changed

+49
-16
lines changed

5 files changed

+49
-16
lines changed

app/api/content-agent/[platform]/route.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { NextRequest } from "next/server";
22
import { after } from "next/server";
3-
import { contentAgentBot } from "@/lib/content-agent/bot";
3+
import { getContentAgentBot } from "@/lib/content-agent/bot";
44
import { handleUrlVerification } from "@/lib/slack/handleUrlVerification";
5-
import "@/lib/content-agent/handlers/registerHandlers";
5+
import { ensureHandlersRegistered } from "@/lib/content-agent/handlers/registerHandlers";
66

77
/**
88
* GET /api/content-agent/[platform]
@@ -19,8 +19,10 @@ export async function GET(
1919
{ params }: { params: Promise<{ platform: string }> },
2020
) {
2121
const { platform } = await params;
22+
ensureHandlersRegistered();
23+
const bot = getContentAgentBot();
2224

23-
const handler = contentAgentBot.webhooks[platform as keyof typeof contentAgentBot.webhooks];
25+
const handler = bot.webhooks[platform as keyof typeof bot.webhooks];
2426

2527
if (!handler) {
2628
return new Response("Unknown platform", { status: 404 });
@@ -51,9 +53,11 @@ export async function POST(
5153
if (verification) return verification;
5254
}
5355

54-
await contentAgentBot.initialize();
56+
ensureHandlersRegistered();
57+
const bot = getContentAgentBot();
58+
await bot.initialize();
5559

56-
const handler = contentAgentBot.webhooks[platform as keyof typeof contentAgentBot.webhooks];
60+
const handler = bot.webhooks[platform as keyof typeof bot.webhooks];
5761

5862
if (!handler) {
5963
return new Response("Unknown platform", { status: 404 });

app/api/content-agent/callback/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { NextRequest } from "next/server";
2-
import { contentAgentBot } from "@/lib/content-agent/bot";
2+
import { getContentAgentBot } from "@/lib/content-agent/bot";
33
import { handleContentAgentCallback } from "@/lib/content-agent/handleContentAgentCallback";
44

55
/**
@@ -12,6 +12,6 @@ import { handleContentAgentCallback } from "@/lib/content-agent/handleContentAge
1212
* @returns The callback response
1313
*/
1414
export async function POST(request: NextRequest) {
15-
await contentAgentBot.initialize();
15+
await getContentAgentBot().initialize();
1616
return handleContentAgentCallback(request);
1717
}

lib/content-agent/bot.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,18 @@ export function createContentAgentBot() {
4747

4848
export type ContentAgentBot = ReturnType<typeof createContentAgentBot>;
4949

50+
let _bot: ContentAgentBot | null = null;
51+
5052
/**
51-
* Singleton bot instance. Registers as the Chat SDK singleton
52-
* so ThreadImpl can resolve adapters lazily from thread IDs.
53+
* Returns the lazily-initialized content agent bot singleton.
54+
* Defers creation until first call so the Vercel build does not
55+
* crash when content-agent env vars are not yet configured.
56+
*
57+
* @returns The content agent bot singleton
5358
*/
54-
export const contentAgentBot = createContentAgentBot().registerSingleton();
59+
export function getContentAgentBot(): ContentAgentBot {
60+
if (!_bot) {
61+
_bot = createContentAgentBot().registerSingleton();
62+
}
63+
return _bot;
64+
}

lib/content-agent/getThread.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
import { ThreadImpl } from "chat";
22
import type { ContentAgentThreadState } from "./types";
33

4+
const THREAD_ID_PATTERN = /^[^:]+:[^:]+:[^:]+$/;
5+
46
/**
57
* Reconstructs a Thread from a stored thread ID using the Chat SDK singleton.
68
*
79
* @param threadId - The stored thread identifier (format: adapter:channel:thread)
810
* @returns The reconstructed Thread instance
11+
* @throws If threadId does not match the expected adapter:channel:thread format
912
*/
1013
export function getThread(threadId: string) {
11-
const adapterName = threadId.split(":")[0];
12-
const channelId = `${adapterName}:${threadId.split(":")[1]}`;
14+
if (!THREAD_ID_PATTERN.test(threadId)) {
15+
throw new Error(
16+
`[content-agent] Invalid threadId format: expected "adapter:channel:thread", got "${threadId}"`,
17+
);
18+
}
19+
20+
const parts = threadId.split(":");
21+
const adapterName = parts[0];
22+
const channelId = `${adapterName}:${parts[1]}`;
23+
1324
return new ThreadImpl<ContentAgentThreadState>({
1425
adapterName,
1526
id: threadId,
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { contentAgentBot } from "../bot";
1+
import { getContentAgentBot } from "../bot";
22
import { registerOnNewMention } from "./handleContentAgentMention";
33
import { registerOnSubscribedMessage } from "./registerOnSubscribedMessage";
44

5+
let registered = false;
6+
57
/**
68
* Registers all content agent event handlers on the bot singleton.
7-
* Import this file once to attach handlers to the bot.
9+
* Safe to call multiple times — handlers are only attached once.
810
*/
9-
registerOnNewMention(contentAgentBot);
10-
registerOnSubscribedMessage(contentAgentBot);
11+
export function ensureHandlersRegistered(): void {
12+
if (registered) return;
13+
registered = true;
14+
15+
const bot = getContentAgentBot();
16+
registerOnNewMention(bot);
17+
registerOnSubscribedMessage(bot);
18+
}

0 commit comments

Comments
 (0)