Skip to content

Conversation

@sweetmantech
Copy link
Contributor

@sweetmantech sweetmantech commented Jan 19, 2026

Summary

  • Add assistant message persistence to /api/chat/generate, matching the pattern from /api/emails/inbound
  • When roomId is provided in the request body, the assistant message is saved to the database
  • Persistence failures are logged but don't fail the request (non-critical operation)

Test plan

  • Verify insertMemories is called when roomId is provided
  • Verify insertMemories is NOT called when roomId is not provided
  • Verify message content is properly filtered with role, parts, and content
  • Verify request still succeeds even if insertMemories fails
  • All 507 recoup-api tests pass

🤖 Generated with Claude Code


Note

Introduces non-blocking assistant message persistence and factors shared logic into a reusable utility.

  • Adds saveChatCompletion to convert textUIMessage, filter content, and insert a memory
  • Updates handleChatGenerate to call saveChatCompletion when roomId is provided; errors are logged but do not affect the response
  • Refactors respondToInboundEmail to use saveChatCompletion instead of inlined message→memory logic
  • Adds focused tests for persistence behavior in handleChatGenerate and for saveChatCompletion end-to-end interactions

Written by Cursor Bugbot for commit e875e24. This will update automatically on new commits. Configure here.

sidneyswift and others added 2 commits January 16, 2026 22:26
Add assistant message persistence to handleChatGenerate, matching the pattern
from /api/emails/inbound. When roomId is provided, the assistant message is
saved to the database using insertMemories with filtered content.

Changes:
- Import insertMemories, getMessages, filterMessageContentForMemories
- After generateText completes, persist assistant message if roomId exists
- Gracefully handle persistence failures (log but don't fail request)
- Add 4 unit tests for message persistence behavior

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@vercel
Copy link

vercel bot commented Jan 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
recoup-api Ready Ready Preview Jan 19, 2026 3:25pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

// as part of the handleChatCredits and handleChatCompletion migrations
// Save assistant message to database if roomId is provided
if (body.roomId) {
const assistantMessage = getMessages(result.text, "assistant")[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes getMessages() will always return an array with at least one element, but it can return an empty array, causing a TypeError when accessing [0].id.

View Details
📝 Patch Details
diff --git a/lib/chat/__tests__/handleChatGenerate.test.ts b/lib/chat/__tests__/handleChatGenerate.test.ts
index 5d9f50b..529eeff 100644
--- a/lib/chat/__tests__/handleChatGenerate.test.ts
+++ b/lib/chat/__tests__/handleChatGenerate.test.ts
@@ -490,5 +490,38 @@ describe("handleChatGenerate", () => {
       const json = await result.json();
       expect(json.text).toBe("Response");
     });
+
+    it("gracefully handles empty text from generateText without crashing", async () => {
+      mockGetApiKeyAccountId.mockResolvedValue("account-123");
+
+      mockSetupChatRequest.mockResolvedValue({
+        model: "gpt-4",
+        instructions: "test",
+        system: "test",
+        messages: [],
+        experimental_generateMessageId: vi.fn(),
+        tools: {},
+        providerOptions: {},
+      } as any);
+
+      mockGenerateText.mockResolvedValue({
+        text: "",
+        finishReason: "stop",
+        usage: { promptTokens: 10, completionTokens: 0 },
+        response: { messages: [], headers: {}, body: null },
+      } as any);
+
+      const request = createMockRequest(
+        { prompt: "Hello", roomId: "room-abc" },
+        { "x-api-key": "valid-key" },
+      );
+
+      const result = await handleChatGenerate(request as any);
+
+      expect(result.status).toBe(200);
+      expect(mockInsertMemories).not.toHaveBeenCalled();
+      const json = await result.json();
+      expect(json.text).toBe("");
+    });
   });
 });
diff --git a/lib/chat/handleChatGenerate.ts b/lib/chat/handleChatGenerate.ts
index cf7c349..b984fba 100644
--- a/lib/chat/handleChatGenerate.ts
+++ b/lib/chat/handleChatGenerate.ts
@@ -34,16 +34,19 @@ export async function handleChatGenerate(request: NextRequest): Promise<Response
 
     // Save assistant message to database if roomId is provided
     if (body.roomId) {
-      const assistantMessage = getMessages(result.text, "assistant")[0];
-      try {
-        await insertMemories({
-          id: assistantMessage.id,
-          room_id: body.roomId,
-          content: filterMessageContentForMemories(assistantMessage),
-        });
-      } catch (error) {
-        // Log error but don't fail the request - message persistence is non-critical
-        console.error("Failed to persist assistant message:", error);
+      const assistantMessages = getMessages(result.text, "assistant");
+      if (assistantMessages.length > 0) {
+        const assistantMessage = assistantMessages[0];
+        try {
+          await insertMemories({
+            id: assistantMessage.id,
+            room_id: body.roomId,
+            content: filterMessageContentForMemories(assistantMessage),
+          });
+        } catch (error) {
+          // Log error but don't fail the request - message persistence is non-critical
+          console.error("Failed to persist assistant message:", error);
+        }
       }
     }
 

Analysis

Missing null check on empty array access causes TypeError in handleChatGenerate

What fails: lib/chat/handleChatGenerate.ts line 37 accesses the first element of an array returned by getMessages() without checking if the array is empty. When result.text is an empty string, getMessages() returns an empty array, causing [0] to be undefined. Accessing .id on undefined throws TypeError: Cannot read properties of undefined (reading 'id').

How to reproduce:

// Call generateText in a scenario where result.text is empty
// This can happen with the AI SDK when using tools with multiple invocations
// (see: https://github.com/vercel/ai/issues/6414)
const result = await generateText({
  // ... config with tools ...
  // If tools make multiple invocations, generateText can return empty text
});
// When result.text === "", getMessages("", "assistant") returns []
// Accessing [0].id on [] throws TypeError

Result: TypeError is caught by outer try-catch (line 68), returning 500 error instead of gracefully handling the edge case.

Expected: Endpoint should return 200 with generated response even if text is empty; message persistence should be skipped when text is empty.

Root cause: Per AI SDK issue #6414, generateText() can return empty text when using tools with multiple invocations. The codebase uses tools (setupChatRequest.ts line 28, 31, 40) without explicit constraints to prevent this scenario.

Fix: Added length check before accessing array element:

const assistantMessages = getMessages(result.text, "assistant");
if (assistantMessages.length > 0) {
  const assistantMessage = assistantMessages[0];
  // ... rest of logic ...
}

This gracefully skips message persistence when text is empty, allowing the endpoint to return 200 with the empty response rather than 500 error.

Extract shared chat completion persistence logic into reusable utility.
Both /api/chat/generate and /api/emails/inbound now use this shared
function instead of duplicating the 3-step process.

- Create lib/chat/saveChatCompletion.ts encapsulating getMessages +
  filterMessageContentForMemories + insertMemories
- Add 6 unit tests for saveChatCompletion
- Update respondToInboundEmail.ts to use saveChatCompletion
- Update handleChatGenerate.ts to use saveChatCompletion
- Update handleChatGenerate.test.ts to mock new utility

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants