diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 973166f0..b6e0f467 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -2,6 +2,7 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import { handleChatStream } from "@/lib/chat/handleChatStream"; +import { handleCreateArtistRedirect } from "@/lib/chat/handleCreateArtistRedirect"; /** * OPTIONS handler for CORS preflight requests. @@ -37,5 +38,16 @@ export async function OPTIONS() { * @returns A streaming response or error */ export async function POST(request: NextRequest): Promise { - return handleChatStream(request); + return handleChatStream(request, async ({ body, responseMessages, writer }) => { + const redirectPath = await handleCreateArtistRedirect(body, responseMessages); + if (!redirectPath) { + return; + } + + writer.write({ + type: "data-redirect", + data: { path: redirectPath }, + transient: true, + }); + }); } diff --git a/lib/chat/__tests__/handleChatCompletion.test.ts b/lib/chat/__tests__/handleChatCompletion.test.ts index aab50328..6da3ee4b 100644 --- a/lib/chat/__tests__/handleChatCompletion.test.ts +++ b/lib/chat/__tests__/handleChatCompletion.test.ts @@ -317,5 +317,36 @@ describe("handleChatCompletion", () => { }), ); }); + + it("returns redirect path after creating and copying the final artist room", async () => { + const body = createMockBody(); + const responseMessages: UIMessage[] = [ + { + id: "resp-1", + role: "assistant", + parts: [ + { + type: "tool-create_new_artist", + toolCallId: "tool-1", + state: "output-available", + input: {}, + output: { + artist: { + account_id: "artist-123", + name: "Test Artist", + }, + artistAccountId: "artist-123", + message: "ok", + }, + } as any, + ], + createdAt: new Date(), + }, + ]; + + await handleChatCompletion(body, responseMessages); + + expect(mockHandleSendEmailToolOutputs).toHaveBeenCalledWith(responseMessages); + }); }); }); diff --git a/lib/chat/__tests__/handleChatStream.test.ts b/lib/chat/__tests__/handleChatStream.test.ts index ab9b9e79..3f805eba 100644 --- a/lib/chat/__tests__/handleChatStream.test.ts +++ b/lib/chat/__tests__/handleChatStream.test.ts @@ -5,6 +5,8 @@ import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId"; import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId"; import { setupChatRequest } from "@/lib/chat/setupChatRequest"; import { setupConversation } from "@/lib/chat/setupConversation"; +import { handleChatCompletion } from "@/lib/chat/handleChatCompletion"; +import { handleCreateArtistRedirect } from "@/lib/chat/handleCreateArtistRedirect"; import { createUIMessageStream, createUIMessageStreamResponse } from "ai"; import { handleChatStream } from "../handleChatStream"; @@ -54,6 +56,10 @@ vi.mock("@/lib/chat/handleChatCompletion", () => ({ handleChatCompletion: vi.fn(), })); +vi.mock("@/lib/chat/handleCreateArtistRedirect", () => ({ + handleCreateArtistRedirect: vi.fn(), +})); + vi.mock("@/lib/credits/handleChatCredits", () => ({ handleChatCredits: vi.fn(), })); @@ -71,6 +77,8 @@ const mockGetApiKeyAccountId = vi.mocked(getApiKeyAccountId); const mockValidateOverrideAccountId = vi.mocked(validateOverrideAccountId); const mockSetupConversation = vi.mocked(setupConversation); const mockSetupChatRequest = vi.mocked(setupChatRequest); +const mockHandleChatCompletion = vi.mocked(handleChatCompletion); +const mockHandleCreateArtistRedirect = vi.mocked(handleCreateArtistRedirect); const mockCreateUIMessageStream = vi.mocked(createUIMessageStream); const mockCreateUIMessageStreamResponse = vi.mocked(createUIMessageStreamResponse); @@ -99,6 +107,8 @@ describe("handleChatStream", () => { roomId: roomId || "mock-room-id", memoryId: "mock-memory-id", })); + mockHandleChatCompletion.mockResolvedValue(); + mockHandleCreateArtistRedirect.mockResolvedValue(undefined); }); afterEach(() => { @@ -247,6 +257,79 @@ describe("handleChatStream", () => { }), ); }); + + it("uses sendFinish false and emits redirect data after completion", async () => { + mockGetApiKeyAccountId.mockResolvedValue("account-123"); + const toUIMessageStream = vi.fn().mockReturnValue(new ReadableStream()); + const mockAgent = { + stream: vi.fn().mockResolvedValue({ + toUIMessageStream, + usage: Promise.resolve({ inputTokens: 100, outputTokens: 50 }), + }), + tools: {}, + }; + + mockSetupChatRequest.mockResolvedValue({ + agent: mockAgent, + messages: [], + } as any); + + const mockStream = new ReadableStream(); + mockCreateUIMessageStream.mockReturnValue(mockStream); + mockCreateUIMessageStreamResponse.mockReturnValue(new Response(mockStream)); + + const request = createMockRequest({ prompt: "Hello" }, { "x-api-key": "valid-key" }); + + await handleChatStream(request as any, async ({ body, responseMessages, writer }) => { + const redirectPath = await handleCreateArtistRedirect(body, responseMessages); + if (!redirectPath) { + return; + } + + writer.write({ + type: "data-redirect", + data: { path: redirectPath }, + transient: true, + }); + }); + + mockHandleCreateArtistRedirect.mockResolvedValue("/chat/new-room-123"); + + const execute = mockCreateUIMessageStream.mock.calls[0][0].execute; + const writer = { + merge: vi.fn(), + write: vi.fn(), + onError: undefined, + }; + + await execute({ writer } as any); + + expect(toUIMessageStream).toHaveBeenCalledWith( + expect.objectContaining({ + sendFinish: false, + onFinish: expect.any(Function), + }), + ); + + const onFinish = toUIMessageStream.mock.calls[0][0].onFinish; + await onFinish({ + isAborted: false, + finishReason: "stop", + messages: [{ id: "resp-1", role: "assistant", parts: [] }], + responseMessage: { id: "resp-1", role: "assistant", parts: [] }, + isContinuation: false, + }); + + expect(writer.write).toHaveBeenCalledWith({ + type: "data-redirect", + data: { path: "/chat/new-room-123" }, + transient: true, + }); + expect(writer.write).toHaveBeenCalledWith({ + type: "finish", + finishReason: "stop", + }); + }); }); describe("error handling", () => { diff --git a/lib/chat/__tests__/handleCreateArtistRedirect.test.ts b/lib/chat/__tests__/handleCreateArtistRedirect.test.ts new file mode 100644 index 00000000..0e5e326a --- /dev/null +++ b/lib/chat/__tests__/handleCreateArtistRedirect.test.ts @@ -0,0 +1,86 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { UIMessage } from "ai"; +import { copyRoom } from "@/lib/rooms/copyRoom"; +import { copyChatMessages } from "@/lib/chats/copyChatMessages"; +import { handleCreateArtistRedirect } from "../handleCreateArtistRedirect"; + +vi.mock("@/lib/rooms/copyRoom", () => ({ + copyRoom: vi.fn(), +})); + +vi.mock("@/lib/chats/copyChatMessages", () => ({ + copyChatMessages: vi.fn(), +})); + +const mockCopyRoom = vi.mocked(copyRoom); +const mockCopyChatMessages = vi.mocked(copyChatMessages); + +describe("handleCreateArtistRedirect", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns redirect path after creating and copying the final artist room", async () => { + mockCopyRoom.mockResolvedValue("new-room-123"); + mockCopyChatMessages.mockResolvedValue({ + status: "success", + copiedCount: 1, + clearedExisting: true, + }); + + const responseMessages: UIMessage[] = [ + { + id: "resp-1", + role: "assistant", + parts: [ + { + type: "tool-create_new_artist", + toolCallId: "tool-1", + state: "output-available", + input: {}, + output: { + artist: { + account_id: "artist-123", + name: "Test Artist", + }, + artistAccountId: "artist-123", + message: "ok", + }, + } as any, + ], + createdAt: new Date(), + }, + ]; + + const result = await handleCreateArtistRedirect( + { + accountId: "account-123", + messages: [], + roomId: "room-456", + } as any, + responseMessages, + ); + + expect(mockCopyRoom).toHaveBeenCalledWith("room-456", "artist-123"); + expect(mockCopyChatMessages).toHaveBeenCalledWith({ + sourceChatId: "room-456", + targetChatId: "new-room-123", + clearExisting: true, + }); + expect(result).toBe("/chat/new-room-123"); + }); + + it("returns undefined when no create artist result exists", async () => { + const result = await handleCreateArtistRedirect( + { + accountId: "account-123", + messages: [], + roomId: "room-456", + } as any, + [{ id: "resp-1", role: "assistant", parts: [], createdAt: new Date() }], + ); + + expect(result).toBeUndefined(); + expect(mockCopyRoom).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/chat/handleChatStream.ts b/lib/chat/handleChatStream.ts index 7319b2cb..aca1381b 100644 --- a/lib/chat/handleChatStream.ts +++ b/lib/chat/handleChatStream.ts @@ -7,6 +7,16 @@ import { setupChatRequest } from "./setupChatRequest"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; import generateUUID from "@/lib/uuid/generateUUID"; import { DEFAULT_MODEL } from "@/lib/const"; +import type { ChatRequestBody } from "./validateChatRequest"; +import type { UIMessage } from "ai"; + +type ChatStreamFinishHandler = (params: { + body: ChatRequestBody; + responseMessages: UIMessage[]; + writer: { + write: (chunk: unknown) => void; + }; +}) => Promise; /** * Handles a streaming chat request. @@ -19,7 +29,10 @@ import { DEFAULT_MODEL } from "@/lib/const"; * @param request - The incoming NextRequest * @returns A streaming response or error NextResponse */ -export async function handleChatStream(request: NextRequest): Promise { +export async function handleChatStream( + request: NextRequest, + onFinish?: ChatStreamFinishHandler, +): Promise { const validatedBodyOrError = await validateChatRequest(request); if (validatedBodyOrError instanceof NextResponse) { return validatedBodyOrError; @@ -38,23 +51,35 @@ export async function handleChatStream(request: NextRequest): Promise execute: async options => { const { writer } = options; streamResult = await agent.stream(chatConfig); - writer.merge(streamResult.toUIMessageStream()); - }, - onFinish: async event => { - if (event.isAborted) { - return; - } - const assistantMessages = event.messages.filter(message => message.role === "assistant"); - const responseMessages = - assistantMessages.length > 0 ? assistantMessages : [event.responseMessage]; - await handleChatCompletion(body, responseMessages); - if (streamResult) { - await handleChatCredits({ - usage: await streamResult.usage, - model: body.model ?? DEFAULT_MODEL, - accountId: body.accountId, - }); - } + writer.merge( + streamResult.toUIMessageStream({ + sendFinish: false, + onFinish: async event => { + if (event.isAborted) { + return; + } + + const assistantMessages = event.messages.filter( + message => message.role === "assistant", + ); + const responseMessages = + assistantMessages.length > 0 ? assistantMessages : [event.responseMessage]; + await handleChatCompletion(body, responseMessages); + await onFinish?.({ body, responseMessages, writer }); + + writer.write({ + type: "finish", + finishReason: event.finishReason, + }); + + await handleChatCredits({ + usage: await streamResult!.usage, + model: body.model ?? DEFAULT_MODEL, + accountId: body.accountId, + }); + }, + }), + ); }, onError: e => { console.error("/api/chat onError:", e); diff --git a/lib/chat/handleCreateArtistRedirect.ts b/lib/chat/handleCreateArtistRedirect.ts new file mode 100644 index 00000000..7fbd24f6 --- /dev/null +++ b/lib/chat/handleCreateArtistRedirect.ts @@ -0,0 +1,59 @@ +import { getToolOrDynamicToolName, isToolOrDynamicToolUIPart, type UIMessage } from "ai"; +import { copyChatMessages } from "@/lib/chats/copyChatMessages"; +import { copyRoom } from "@/lib/rooms/copyRoom"; +import type { ChatRequestBody } from "./validateChatRequest"; +import type { CreateNewArtistResult } from "@/lib/mcp/tools/artists/registerCreateNewArtistTool"; + +function getCreateArtistResult(responseMessages: UIMessage[]): CreateNewArtistResult | null { + for (const message of responseMessages) { + for (const part of message.parts) { + if (!isToolOrDynamicToolUIPart(part)) continue; + if (getToolOrDynamicToolName(part) !== "create_new_artist") continue; + if (part.state !== "output-available") continue; + + if (part.type === "dynamic-tool") { + const text = (part.output as { content?: Array<{ text?: string }> } | undefined) + ?.content?.[0]?.text; + if (!text) continue; + + try { + return JSON.parse(text) as CreateNewArtistResult; + } catch { + continue; + } + } + + return part.output as CreateNewArtistResult; + } + } + + return null; +} + +export async function handleCreateArtistRedirect( + body: ChatRequestBody, + responseMessages: UIMessage[], +): Promise { + const createArtistResult = getCreateArtistResult(responseMessages); + if (!createArtistResult?.artistAccountId) { + return undefined; + } + + const newRoomId = await copyRoom(body.roomId ?? "", createArtistResult.artistAccountId); + if (!newRoomId) { + console.error("Failed to create final artist conversation room"); + return undefined; + } + + const copyResult = await copyChatMessages({ + sourceChatId: body.roomId ?? "", + targetChatId: newRoomId, + clearExisting: true, + }); + if (copyResult.status === "error") { + console.error(copyResult.error); + return undefined; + } + + return `/chat/${newRoomId}`; +} diff --git a/lib/chats/__tests__/copyChatMessagesHandler.test.ts b/lib/chats/__tests__/copyChatMessagesHandler.test.ts index 77c72a49..f6ca9c4d 100644 --- a/lib/chats/__tests__/copyChatMessagesHandler.test.ts +++ b/lib/chats/__tests__/copyChatMessagesHandler.test.ts @@ -3,10 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; import { copyChatMessagesHandler } from "@/lib/chats/copyChatMessagesHandler"; import { validateCopyChatMessagesBody } from "@/lib/chats/validateCopyChatMessagesBody"; import { validateChatAccess } from "@/lib/chats/validateChatAccess"; -import selectMemories from "@/lib/supabase/memories/selectMemories"; -import deleteMemories from "@/lib/supabase/memories/deleteMemories"; -import insertMemories from "@/lib/supabase/memories/insertMemories"; -import { generateUUID } from "@/lib/uuid/generateUUID"; +import { copyChatMessages } from "@/lib/chats/copyChatMessages"; vi.mock("@/lib/networking/getCorsHeaders", () => ({ getCorsHeaders: vi.fn(() => ({ "Access-Control-Allow-Origin": "*" })), @@ -20,20 +17,8 @@ vi.mock("@/lib/chats/validateChatAccess", () => ({ validateChatAccess: vi.fn(), })); -vi.mock("@/lib/supabase/memories/selectMemories", () => ({ - default: vi.fn(), -})); - -vi.mock("@/lib/supabase/memories/deleteMemories", () => ({ - default: vi.fn(), -})); - -vi.mock("@/lib/supabase/memories/insertMemories", () => ({ - default: vi.fn(), -})); - -vi.mock("@/lib/uuid/generateUUID", () => ({ - generateUUID: vi.fn(), +vi.mock("@/lib/chats/copyChatMessages", () => ({ + copyChatMessages: vi.fn(), })); const sourceChatId = "123e4567-e89b-42d3-a456-426614174000"; @@ -71,7 +56,7 @@ describe("copyChatMessagesHandler", () => { expect(response.status).toBe(400); expect(body.error).toBe("Invalid body"); - expect(selectMemories).not.toHaveBeenCalled(); + expect(copyChatMessages).not.toHaveBeenCalled(); }); it("returns 500 when source messages cannot be loaded", async () => { @@ -81,7 +66,10 @@ describe("copyChatMessagesHandler", () => { }); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(sourceChatId)); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(targetChatId)); - vi.mocked(selectMemories).mockResolvedValue(null); + vi.mocked(copyChatMessages).mockResolvedValue({ + status: "error", + error: "Failed to load source chat messages", + }); const response = await copyChatMessagesHandler(request, sourceChatId); const body = await response.json(); @@ -97,32 +85,21 @@ describe("copyChatMessagesHandler", () => { }); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(sourceChatId)); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(targetChatId)); - vi.mocked(selectMemories).mockResolvedValue([ - { - id: "mem-1", - room_id: sourceChatId, - content: { role: "user", content: "hello" }, - updated_at: "2026-03-31T00:00:00.000Z", - created_at: "2026-03-31T00:00:00.000Z", - }, - ]); - vi.mocked(deleteMemories).mockResolvedValue(true); - vi.mocked(generateUUID).mockReturnValue("new-mem-1"); - vi.mocked(insertMemories).mockResolvedValue(1); + vi.mocked(copyChatMessages).mockResolvedValue({ + status: "success", + copiedCount: 1, + clearedExisting: true, + }); const response = await copyChatMessagesHandler(request, sourceChatId); const body = await response.json(); expect(response.status).toBe(200); - expect(deleteMemories).toHaveBeenCalledWith(targetChatId); - expect(insertMemories).toHaveBeenCalledWith([ - { - id: "new-mem-1", - room_id: targetChatId, - content: { role: "user", content: "hello" }, - updated_at: "2026-03-31T00:00:00.000Z", - }, - ]); + expect(copyChatMessages).toHaveBeenCalledWith({ + sourceChatId, + targetChatId, + clearExisting: true, + }); expect(body).toEqual({ status: "success", source_chat_id: sourceChatId, @@ -139,15 +116,16 @@ describe("copyChatMessagesHandler", () => { }); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(sourceChatId)); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(targetChatId)); - vi.mocked(selectMemories).mockResolvedValue([]); - vi.mocked(deleteMemories).mockResolvedValue(false); + vi.mocked(copyChatMessages).mockResolvedValue({ + status: "error", + error: "Failed to clear target chat messages", + }); const response = await copyChatMessagesHandler(request, sourceChatId); const body = await response.json(); expect(response.status).toBe(500); expect(body.error).toBe("Failed to clear target chat messages"); - expect(insertMemories).not.toHaveBeenCalled(); }); it("skips clear when clearExisting is false", async () => { @@ -157,14 +135,16 @@ describe("copyChatMessagesHandler", () => { }); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(sourceChatId)); vi.mocked(validateChatAccess).mockResolvedValueOnce(accessRoom(targetChatId)); - vi.mocked(selectMemories).mockResolvedValue([]); - vi.mocked(insertMemories).mockResolvedValue(0); + vi.mocked(copyChatMessages).mockResolvedValue({ + status: "success", + copiedCount: 0, + clearedExisting: false, + }); const response = await copyChatMessagesHandler(request, sourceChatId); const body = await response.json(); expect(response.status).toBe(200); - expect(deleteMemories).not.toHaveBeenCalled(); expect(body.copied_count).toBe(0); expect(body.cleared_existing).toBe(false); }); @@ -194,7 +174,7 @@ describe("copyChatMessagesHandler", () => { const response = await copyChatMessagesHandler(request, sourceChatId); expect(response.status).toBe(401); - expect(selectMemories).not.toHaveBeenCalled(); + expect(copyChatMessages).not.toHaveBeenCalled(); }); it("passes through target chat access errors", async () => { @@ -210,6 +190,6 @@ describe("copyChatMessagesHandler", () => { const response = await copyChatMessagesHandler(request, sourceChatId); expect(response.status).toBe(403); - expect(selectMemories).not.toHaveBeenCalled(); + expect(copyChatMessages).not.toHaveBeenCalled(); }); }); diff --git a/lib/chats/copyChatMessages.ts b/lib/chats/copyChatMessages.ts new file mode 100644 index 00000000..40000d5f --- /dev/null +++ b/lib/chats/copyChatMessages.ts @@ -0,0 +1,54 @@ +import selectMemories from "@/lib/supabase/memories/selectMemories"; +import deleteMemories from "@/lib/supabase/memories/deleteMemories"; +import insertMemories from "@/lib/supabase/memories/insertMemories"; +import { generateUUID } from "@/lib/uuid/generateUUID"; + +type CopyChatMessagesParams = { + sourceChatId: string; + targetChatId: string; + clearExisting?: boolean; +}; + +export type CopyChatMessagesResult = + | { + status: "success"; + copiedCount: number; + clearedExisting: boolean; + } + | { + status: "error"; + error: string; + }; + +export async function copyChatMessages({ + sourceChatId, + targetChatId, + clearExisting = true, +}: CopyChatMessagesParams): Promise { + const sourceMemories = await selectMemories(sourceChatId, { ascending: true }); + if (!sourceMemories) { + return { status: "error", error: "Failed to load source chat messages" }; + } + + if (clearExisting) { + const deleted = await deleteMemories(targetChatId); + if (!deleted) { + return { status: "error", error: "Failed to clear target chat messages" }; + } + } + + const copiedCount = await insertMemories( + sourceMemories.map(memory => ({ + id: generateUUID(), + room_id: targetChatId, + content: memory.content, + updated_at: memory.updated_at, + })), + ); + + return { + status: "success", + copiedCount, + clearedExisting: clearExisting, + }; +} diff --git a/lib/chats/copyChatMessagesHandler.ts b/lib/chats/copyChatMessagesHandler.ts index 0fa3fd43..2dc0489e 100644 --- a/lib/chats/copyChatMessagesHandler.ts +++ b/lib/chats/copyChatMessagesHandler.ts @@ -1,12 +1,9 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; -import { generateUUID } from "@/lib/uuid/generateUUID"; -import selectMemories from "@/lib/supabase/memories/selectMemories"; -import insertMemories from "@/lib/supabase/memories/insertMemories"; -import deleteMemories from "@/lib/supabase/memories/deleteMemories"; import { validateCopyChatMessagesBody } from "@/lib/chats/validateCopyChatMessagesBody"; import { validateChatAccess } from "@/lib/chats/validateChatAccess"; +import { copyChatMessages } from "@/lib/chats/copyChatMessages"; /** * Handles POST /api/chats/[id]/messages/copy. @@ -37,42 +34,26 @@ export async function copyChatMessagesHandler( const accessibleSourceChatId = sourceAccess.roomId; const accessibleTargetChatId = targetAccess.roomId; - const { clearExisting } = validated; + const copyResult = await copyChatMessages({ + sourceChatId: accessibleSourceChatId, + targetChatId: accessibleTargetChatId, + clearExisting: validated.clearExisting, + }); - const sourceMemories = await selectMemories(accessibleSourceChatId, { ascending: true }); - if (!sourceMemories) { + if (copyResult.status === "error") { return NextResponse.json( - { status: "error", error: "Failed to load source chat messages" }, + { status: "error", error: copyResult.error }, { status: 500, headers: getCorsHeaders() }, ); } - if (clearExisting) { - const deleted = await deleteMemories(accessibleTargetChatId); - if (!deleted) { - return NextResponse.json( - { status: "error", error: "Failed to clear target chat messages" }, - { status: 500, headers: getCorsHeaders() }, - ); - } - } - - const copiedCount = await insertMemories( - sourceMemories.map(memory => ({ - id: generateUUID(), - room_id: accessibleTargetChatId, - content: memory.content, - updated_at: memory.updated_at, - })), - ); - return NextResponse.json( { status: "success", source_chat_id: accessibleSourceChatId, target_chat_id: accessibleTargetChatId, - copied_count: copiedCount, - cleared_existing: clearExisting, + copied_count: copyResult.copiedCount, + cleared_existing: copyResult.clearedExisting, }, { status: 200, headers: getCorsHeaders() }, ); diff --git a/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts b/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts index 47e912af..c5523245 100644 --- a/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts +++ b/lib/mcp/tools/artists/__tests__/registerCreateNewArtistTool.test.ts @@ -6,17 +6,12 @@ import type { ServerRequest, ServerNotification } from "@modelcontextprotocol/sd import { registerCreateNewArtistTool } from "../registerCreateNewArtistTool"; const mockCreateArtistInDb = vi.fn(); -const mockCopyRoom = vi.fn(); const mockCanAccessAccount = vi.fn(); vi.mock("@/lib/artists/createArtistInDb", () => ({ createArtistInDb: (...args: unknown[]) => mockCreateArtistInDb(...args), })); -vi.mock("@/lib/rooms/copyRoom", () => ({ - copyRoom: (...args: unknown[]) => mockCopyRoom(...args), -})); - vi.mock("@/lib/organizations/canAccessAccount", () => ({ canAccessAccount: (...args: unknown[]) => mockCanAccessAccount(...args), })); @@ -132,7 +127,7 @@ describe("registerCreateNewArtistTool", () => { }); }); - it("copies room when active_conversation_id is provided", async () => { + it("does not create a room when active_conversation_id is provided", async () => { const mockArtist = { id: "artist-123", account_id: "artist-123", @@ -141,7 +136,6 @@ describe("registerCreateNewArtistTool", () => { account_socials: [], }; mockCreateArtistInDb.mockResolvedValue(mockArtist); - mockCopyRoom.mockResolvedValue("new-room-789"); const result = await registeredHandler( { @@ -152,12 +146,19 @@ describe("registerCreateNewArtistTool", () => { createMockExtra(), ); - expect(mockCopyRoom).toHaveBeenCalledWith("source-room-111", "artist-123"); expect(result).toEqual({ content: [ { type: "text", - text: expect.stringContaining("new-room-789"), + text: expect.stringContaining('"artistAccountId":"artist-123"'), + }, + ], + }); + expect(result).toEqual({ + content: [ + { + type: "text", + text: expect.not.stringContaining("newRoomId"), }, ], }); diff --git a/lib/mcp/tools/artists/registerCreateNewArtistTool.ts b/lib/mcp/tools/artists/registerCreateNewArtistTool.ts index 7495bcbf..2b1ed4a4 100644 --- a/lib/mcp/tools/artists/registerCreateNewArtistTool.ts +++ b/lib/mcp/tools/artists/registerCreateNewArtistTool.ts @@ -5,7 +5,6 @@ import { z } from "zod"; import type { McpAuthInfo } from "@/lib/mcp/verifyApiKey"; import { resolveAccountId } from "@/lib/mcp/resolveAccountId"; import { createArtistInDb, type CreateArtistResult } from "@/lib/artists/createArtistInDb"; -import { copyRoom } from "@/lib/rooms/copyRoom"; import { getToolResultSuccess } from "@/lib/mcp/getToolResultSuccess"; import { getToolResultError } from "@/lib/mcp/getToolResultError"; @@ -44,7 +43,6 @@ export type CreateNewArtistResult = { artistAccountId?: string; message: string; error?: string; - newRoomId?: string | null; }; /** @@ -71,7 +69,7 @@ export function registerCreateNewArtistTool(server: McpServer): void { extra: RequestHandlerExtra, ) => { try { - const { name, account_id, active_conversation_id, organization_id } = args; + const { name, account_id, organization_id } = args; // Resolve accountId from auth or use provided account_id const authInfo = extra.authInfo as McpAuthInfo | undefined; @@ -99,12 +97,6 @@ export function registerCreateNewArtistTool(server: McpServer): void { return getToolResultError("Failed to create artist"); } - // Copy the conversation to the new artist if requested - let newRoomId: string | null = null; - if (active_conversation_id) { - newRoomId = await copyRoom(active_conversation_id, artist.account_id); - } - const result: CreateNewArtistResult = { artist: { account_id: artist.account_id, @@ -113,7 +105,6 @@ export function registerCreateNewArtistTool(server: McpServer): void { }, artistAccountId: artist.account_id, message: `Successfully created artist "${name}". Now searching Spotify for this artist to connect their profile...`, - newRoomId, }; return getToolResultSuccess(result);