Skip to content

feat: migrate chat types from Recoup-Chat to recoup-api#112

Merged
sweetmantech merged 19 commits intotestfrom
sweetmantech/myc-3520-api-migrate-apichat-endpoints-from-recoup-chat-to-recoup-api
Jan 15, 2026
Merged

feat: migrate chat types from Recoup-Chat to recoup-api#112
sweetmantech merged 19 commits intotestfrom
sweetmantech/myc-3520-api-migrate-apichat-endpoints-from-recoup-chat-to-recoup-api

Conversation

@sweetmantech
Copy link
Contributor

@sweetmantech sweetmantech commented Jan 14, 2026

Summary

  • Add ChatConfig interface to lib/chat/types.ts that extends RoutingDecision
  • Add 10 unit tests for chat types in lib/chat/__tests__/types.test.ts
  • Part of the chat endpoint migration from Recoup-Chat to recoup-api

Test plan

  • Run pnpm test - all 108 tests pass
  • Verify types compile correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Chat streaming and non‑streaming endpoints with improved request validation, auth (including account-override), and CORS handling.
    • Automatic chat topic generation from the first message with graceful fallback.
    • Expanded chat configuration, provider options, experimental hooks, and MAX_MESSAGES cap (55).
    • Background handlers: post-chat processing, credit accounting, email-send result handling, and Telegram error notifications.
  • Utilities

    • Model discovery and filtering helpers; message/image/knowledge extraction and validation utilities.
    • Error serialization and DB upsert/select helpers.
  • Tests

    • Extensive unit tests covering validation, setup, streaming/generation, post‑processing, utilities, and title generation.

✏️ Tip: You can customize this high-level summary in your review settings.

Add ChatConfig interface that extends RoutingDecision with:
- system prompt, messages, and tools
- experimental_generateMessageId and experimental_download functions
- prepareStep function and providerOptions

Add 10 unit tests for the chat types.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Jan 14, 2026

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

Project Deployment Review Updated (UTC)
recoup-api Ready Ready Preview Jan 15, 2026 2:34am

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dd3a150 and 94286aa.

📒 Files selected for processing (35)
  • lib/agents/googleSheetsAgent/__tests__/getGoogleSheetsTools.test.ts
  • lib/agents/googleSheetsAgent/getGoogleSheetsTools.ts
  • lib/agents/googleSheetsAgent/index.ts
  • lib/chat/__tests__/handleChatGenerate.test.ts
  • lib/chat/__tests__/handleChatStream.test.ts
  • lib/chat/__tests__/integration/chatEndToEnd.test.ts
  • lib/chat/__tests__/setupChatRequest.test.ts
  • lib/chat/__tests__/setupToolsForRequest.test.ts
  • lib/chat/__tests__/validateChatRequest.test.ts
  • lib/chat/setupChatRequest.ts
  • lib/chat/setupToolsForRequest.ts
  • lib/chat/toolChains/__tests__/getExecutedToolTimeline.test.ts
  • lib/chat/toolChains/__tests__/getPrepareStepResult.test.ts
  • lib/chat/toolChains/__tests__/toolChains.test.ts
  • lib/chat/toolChains/createNewArtistToolChain.ts
  • lib/chat/toolChains/create_release_report/createReleaseReportToolChain.ts
  • lib/chat/toolChains/create_release_report/getReleaseReportReferenceMessage.ts
  • lib/chat/toolChains/create_release_report/referenceReleaseReport.ts
  • lib/chat/toolChains/getExecutedToolTimeline.ts
  • lib/chat/toolChains/getKnowledgeBaseReportReferenceMessage.ts
  • lib/chat/toolChains/getPrepareStepResult.ts
  • lib/chat/toolChains/index.ts
  • lib/chat/toolChains/knowledgeBaseReferenceReport.ts
  • lib/chat/toolChains/toolChains.ts
  • lib/chat/validateChatRequest.ts
  • lib/composio/tools/googleSheetsLoginTool.ts
  • lib/mcp/tools/__tests__/registerArtistDeepResearchTool.test.ts
  • lib/mcp/tools/__tests__/registerWebDeepResearchTool.test.ts
  • lib/mcp/tools/index.ts
  • lib/mcp/tools/registerArtistDeepResearchTool.ts
  • lib/mcp/tools/registerWebDeepResearchTool.ts
  • lib/messages/__tests__/getLatestUserMessageText.test.ts
  • lib/messages/getLatestUserMessageText.ts
  • lib/perplexity/__tests__/chatWithPerplexity.test.ts
  • lib/perplexity/chatWithPerplexity.ts
📝 Walkthrough

Walkthrough

Adds request validation and assembly of a ChatConfig, streaming and non-streaming chat handlers and routes, chat title generation, MAX_MESSAGES constant, numerous chat/email/credit utilities, Supabase helpers, Telegram error notification, error serialization, and extensive unit tests across these flows.

Changes

Cohort / File(s) Summary
Types & Constants
lib/chat/types.ts, lib/chat/const.ts
Exported ChatConfig interface (extends RoutingDecision) added; new MAX_MESSAGES = 55.
Request validation
lib/chat/validateChatRequest.ts, lib/chat/__tests__/validateChatRequest.test.ts
New validateChatRequest(request) with schema validation, auth via getApiKeyAccountId, optional account override via validateOverrideAccountId, prompt→messages normalization; tests added.
Setup & config assembly
lib/chat/setupChatRequest.ts, lib/chat/__tests__/setupChatRequest.test.ts
New setupChatRequest(body) obtains routing decision via getGeneralAgent, converts messages, caps to MAX_MESSAGES, builds ChatConfig (including experimental hooks and providerOptions); tests added.
Handlers & routes (stream + non-stream)
lib/chat/handleChatGenerate.ts, lib/chat/handleChatStream.ts, app/api/chat/route.ts, app/api/chat/generate/route.ts, lib/chat/__tests__/handleChatGenerate.test.ts, lib/chat/__tests__/handleChatStream.test.ts
New server handlers handleChatGenerate and handleChatStream; route files with OPTIONS + POST handlers; integrated validation, setup, generation/streaming, CORS headers, error handling; extensive tests added.
Type tests
lib/chat/__tests__/types.test.ts
Unit tests for RoutingDecision and new ChatConfig shape.
Chat title generation & create flow
lib/chats/generateChatTitle.ts, lib/chats/createChatHandler.ts, lib/chats/validateCreateChatBody.ts, lib/chats/__tests__/*
New generateChatTitle(question) exported; createChatHandler optionally invokes it when firstMessage present (with try/catch fallback); schema accepts optional firstMessage; tests added.
Message/validation utilities
lib/chat/validateMessages.ts, lib/messages/..., lib/chat/__tests__/buildSystemPromptWithImages.test.ts
Added validateMessages (checks/filters UIMessage array); numerous tests for image extraction and system prompt building.
Credits & billing
lib/credits/getCreditUsage.ts, lib/credits/handleChatCredits.ts, lib/credits/__tests__/*
New getCreditUsage computing cost from model pricing and usage; handleChatCredits to deduct credits (min 1); tests added.
AI model helpers
lib/ai/getAvailableModels.ts, lib/ai/getModel.ts, lib/ai/isEmbedModel.ts, lib/ai/__tests__/*
New model discovery helpers and isEmbedModel utility; tests for model filtering/lookup.
Email tooling
lib/emails/extractSendEmailResults.ts, lib/emails/handleSendEmailToolOutputs.ts
Utilities to extract send_email tool outputs and persist them via insertMemoryEmail; exported functions added.
Chat completion post-processing
lib/chat/handleChatCompletion.ts, lib/chat/__tests__/handleChatCompletion.test.ts
New handleChatCompletion to store memories, create rooms, send notifications, process tool outputs; tests added.
Supabase helpers
lib/supabase/memories/upsertMemory.ts, lib/supabase/rooms/selectRoom.ts
New upsert/select helpers for memories and rooms (typed with DB tables).
Error & telemetry
lib/errors/serializeError.ts, lib/telegram/sendErrorNotification.ts
Added error serialization and Telegram notification helper with formatting and safe send.
Files / KB helper
lib/files/__tests__/getKnowledgeBaseText.test.ts
Tests for knowledge-base text fetching and formatting (new/updated tests).
Package
package.json
Added AI SDK dependencies: @ai-sdk/anthropic, @ai-sdk/gateway, @ai-sdk/google, @ai-sdk/openai.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Validator as validateChatRequest
  participant Auth as getApiKeyAccountId
  participant Override as validateOverrideAccountId
  participant Setup as setupChatRequest
  participant Agent as getGeneralAgent
  participant Converter as convertToModelMessages
  participant Gen as generateText
  participant Response

  Client->>Validator: POST chat request
  Validator->>Validator: validate body (chatRequestSchema)
  alt validation fails
    Validator->>Client: return NextResponse error (with CORS)
  else
    Validator->>Auth: resolve accountId
    alt override requested
      Validator->>Override: validate override
      Override-->>Validator: allowed/denied
    end
    Validator->>Setup: pass validated body (with accountId)
    Setup->>Agent: getGeneralAgent(body)
    Agent-->>Setup: routingDecision (model,instructions,agent/tools)
    Setup->>Converter: convertToModelMessages(messages, tools)
    Converter-->>Setup: model-compatible messages
    Setup-->>Validator: ChatConfig
    Validator->>Gen: generateText using ChatConfig
    Gen-->>Validator: result (text/reasoning/usage)
    Validator->>Response: return Response JSON (with CORS)
  end
Loading
sequenceDiagram
  participant Client
  participant StreamHandler as handleChatStream
  participant Validator as validateChatRequest
  participant Setup as setupChatRequest
  participant Agent as routingAgent
  participant UIStream as createUIMessageStream
  participant Response

  Client->>StreamHandler: POST streaming chat request
  StreamHandler->>Validator: validateChatRequest
  Validator-->>StreamHandler: ChatRequestBody
  StreamHandler->>Setup: setupChatRequest
  Setup-->>StreamHandler: ChatConfig
  StreamHandler->>UIStream: createUIMessageStream()
  StreamHandler->>Agent: agent.stream(ChatConfig)
  Agent-->>UIStream: stream events (tokens, tool calls)
  UIStream-->>Response: streaming HTTP response (with CORS & headers)
  Response-->>Client: streamed messages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇
I hopped through routes and typed each stride,
Prompts to messages, tools close at my side,
Titles trimmed tidy, streams flowing bright,
Keys checked, configs built — code humming at night,
A rabbit cheers as tests take flight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main objective: migrating chat type definitions from Recoup-Chat to recoup-api, which aligns with the primary change of adding ChatConfig interface and tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Add validateChatRequest function to recoup-api that:
- Validates request body using Zod schema (prompt/messages mutual exclusivity)
- Authenticates via API key using getApiKeyAccountId
- Supports accountId override for org API keys via validateOverrideAccountId
- Normalizes prompt to messages array using getMessages

Includes 18 unit tests covering schema validation, authentication,
accountId override, message normalization, and optional fields.

Part of MYC-3520 API chat endpoint migration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/chat/validateChatRequest.ts`:
- Around line 58-59: The code calls await request.json() without handling parse
errors; wrap the JSON parsing in a try/catch inside validateChatRequest (around
request.json()) and if parsing throws (SyntaxError) return a 400-style
validation response with a clear message before calling
chatRequestSchema.safeParse(json); ensure you still call
chatRequestSchema.safeParse(json) only on successfully parsed JSON and propagate
parse error details in the 400 response to aid debugging.
🧹 Nitpick comments (3)
lib/chat/validateChatRequest.ts (1)

13-13: Consider stricter typing for messages array elements.

Using z.array(z.any()) provides flexibility but loses type safety at the schema level. If the message format is known (e.g., { role: string, content: string }), consider defining a more specific schema to catch malformed messages early.

lib/chat/__tests__/validateChatRequest.test.ts (2)

20-29: Minor type inconsistency in mock helper.

The helper returns Request but validateChatRequest expects NextRequest. While the as any casts in tests work around this, updating the return type annotation to match would improve clarity.

-function createMockRequest(body: unknown, headers: Record<string, string> = {}): Request {
+function createMockRequest(body: unknown, headers: Record<string, string> = {}): unknown {

Or import and use NextRequest type if more properties are needed in future tests.


31-34: Consider adding test for invalid JSON body.

Once the JSON parsing error handling is added to validateChatRequest, add a corresponding test to verify malformed JSON returns a 400 error:

it("rejects request with invalid JSON body", async () => {
  const request = {
    json: () => Promise.reject(new SyntaxError("Unexpected token")),
    headers: {
      get: () => "test-key",
      has: () => true,
    },
  } as unknown as Request;

  const result = await validateChatRequest(request as any);

  expect(result).toBeInstanceOf(NextResponse);
  const json = await (result as NextResponse).json();
  expect(json.status).toBe("error");
  expect(json.message).toBe("Invalid JSON body");
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b454517 and 8385ca5.

📒 Files selected for processing (2)
  • lib/chat/__tests__/validateChatRequest.test.ts
  • lib/chat/validateChatRequest.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Apply Single Responsibility Principle (SRP) - one exported function per file
Apply DRY principle (Don't Repeat Yourself) - extract shared logic into reusable utilities

Files:

  • lib/chat/validateChatRequest.ts
  • lib/chat/__tests__/validateChatRequest.test.ts
!(lib/supabase)/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER import @/lib/supabase/serverClient outside of lib/supabase/ directory

Files:

  • lib/chat/validateChatRequest.ts
  • lib/chat/__tests__/validateChatRequest.test.ts
🧠 Learnings (2)
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/validation/**/*.{ts,tsx} : Create `validate<EndpointName>Body.ts` files for POST/PUT request body validation

Applied to files:

  • lib/chat/validateChatRequest.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to app/api/**/*.{ts,tsx} : All API endpoints should use a validate function for input parsing with Zod for schema validation

Applied to files:

  • lib/chat/validateChatRequest.ts
  • lib/chat/__tests__/validateChatRequest.test.ts
🧬 Code graph analysis (1)
lib/chat/validateChatRequest.ts (4)
lib/networking/getCorsHeaders.ts (1)
  • getCorsHeaders (6-12)
lib/auth/getApiKeyAccountId.ts (1)
  • getApiKeyAccountId (14-77)
lib/accounts/validateOverrideAccountId.ts (1)
  • validateOverrideAccountId (28-78)
lib/messages/getMessages.ts (1)
  • getMessages (12-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (8)
lib/chat/validateChatRequest.ts (2)

1-7: LGTM!

Imports are correctly structured and follow the coding guidelines. The file does not import from @/lib/supabase/serverClient outside the supabase directory, which is correct per the guidelines.


100-116: LGTM!

The message normalization logic correctly handles the conversion from prompt to messages format, and the final return properly spreads the validated body with the resolved accountId.

lib/chat/__tests__/validateChatRequest.test.ts (6)

36-99: LGTM!

The schema validation tests comprehensively cover the mutual exclusivity constraint between messages and prompt, including all four combinations (neither, both, messages-only, prompt-only).


101-150: LGTM!

Authentication tests properly verify the three key scenarios: missing header, invalid key, and valid key with correct accountId extraction.


152-195: LGTM!

The accountId override tests verify both successful org API key overrides and proper rejection of unauthorized override attempts with correct error messaging.


197-231: LGTM!

Message normalization tests correctly verify that prompts are converted to the UIMessage format with parts[0].text structure, and that provided messages arrays are preserved unchanged.


233-289: LGTM!

Optional fields tests provide complete coverage for all routing/context fields (roomId, artistId, model, excludeTools), verifying they pass through correctly.


291-312: LGTM!

Schema export tests ensure chatRequestSchema is available for external validation and correctly enforces mutual exclusivity, which is useful for consumers who may want to validate before making requests.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +58 to +59
const json = await request.json();
const validationResult = chatRequestSchema.safeParse(json);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling for JSON parsing.

request.json() throws a SyntaxError if the request body is not valid JSON. This will result in an unhandled exception and a 500 error instead of a descriptive 400 response.

Proposed fix
 export async function validateChatRequest(
   request: NextRequest,
 ): Promise<Response | ChatRequestBody> {
-  const json = await request.json();
+  let json: unknown;
+  try {
+    json = await request.json();
+  } catch {
+    return NextResponse.json(
+      {
+        status: "error",
+        message: "Invalid JSON body",
+      },
+      {
+        status: 400,
+        headers: getCorsHeaders(),
+      },
+    );
+  }
   const validationResult = chatRequestSchema.safeParse(json);
🤖 Prompt for AI Agents
In `@lib/chat/validateChatRequest.ts` around lines 58 - 59, The code calls await
request.json() without handling parse errors; wrap the JSON parsing in a
try/catch inside validateChatRequest (around request.json()) and if parsing
throws (SyntaxError) return a 400-style validation response with a clear message
before calling chatRequestSchema.safeParse(json); ensure you still call
chatRequestSchema.safeParse(json) only on successfully parsed JSON and propagate
parse error details in the 400 response to aid debugging.

Add setupChatRequest that prepares chat configuration:
- Calls getGeneralAgent for routing decision
- Converts messages with MAX_MESSAGES limit (55)
- Configures provider options for Anthropic/Google/OpenAI
- Includes experimental_generateMessageId

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements POST /api/chat with createUIMessageStream for real-time
streaming responses using the existing chat infrastructure.

- Add handleChatStream function for streaming chat processing
- Add route handler with CORS support
- Add 7 unit tests covering validation, streaming, and error handling

Note: handleChatCredits and handleChatCompletion hooks are no-ops
until those functions are migrated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add POST /api/chat/generate endpoint for synchronous text generation:
- handleChatGenerate function using AI SDK's generateText
- Returns JSON with text, reasoningText, sources, finishReason, usage
- Validates via validateChatRequest, configures via setupChatRequest
- Includes 9 unit tests covering validation, generation, and error handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add generateChatTitle function using LIGHTWEIGHT_MODEL (gpt-4o-mini)
- Generate formal titles under 20 characters from firstMessage
- Highlight segment names if present in the message
- Strip accidental quote wrapping from generated titles
- Add firstMessage field to createChatBody schema
- Integrate title generation into createChatHandler
- Gracefully fall back to null topic on generation failure
- Add 21 new unit tests (15 for generateChatTitle, 6 for validation/handler)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@lib/chat/handleChatGenerate.ts`:
- Around line 41-45: The API is returning non-serializable data:
result.response.headers (likely a Headers instance) and result.response.body
(possibly a ReadableStream) will not JSON-serialize correctly; update the
response construction in handleChatGenerate (the response: { messages, headers,
body } block) to convert headers to a plain object (e.g., iterate
result.response.headers and build a key/value map) and either read/serialize the
body to a string/JSON (if small and safe) or omit/replace it with a safe
indicator (e.g., null or a size/type descriptor) to ensure the returned payload
is fully serializable.

In `@lib/chat/handleChatStream.ts`:
- Line 49: The successful streaming response omits CORS headers while the error
path uses getCorsHeaders(); update the success return in handleChatStream to
pass headers by calling createUIMessageStreamResponse with the headers option
(use getCorsHeaders()) so both success and error responses include consistent
CORS headers, locating the change at the createUIMessageStreamResponse({ stream
}) call.
🧹 Nitpick comments (7)
lib/chats/generateChatTitle.ts (1)

11-20: Consider sanitizing user input to mitigate prompt injection.

The question parameter is directly interpolated into the prompt without sanitization. While this is used for title generation (low-risk), a malicious firstMessage could potentially manipulate the LLM's behavior.

Additionally, consider trimming the result to handle any leading/trailing whitespace the model might generate:

♻️ Suggested improvement
   // In case model accidentally generates quotes again, remove them here
-  return response.text.replace(/^["']|["']$/g, "");
+  return response.text.replace(/^["']|["']$/g, "").trim();
lib/chats/__tests__/generateChatTitle.test.ts (1)

43-55: Consider importing LIGHTWEIGHT_MODEL constant instead of hardcoding.

The test hardcodes "gpt-4o-mini" which creates fragile coupling. If LIGHTWEIGHT_MODEL in lib/const.ts changes, this test would incorrectly pass.

♻️ Suggested improvement
+import { LIGHTWEIGHT_MODEL } from "@/lib/const";
+
 // ... in the test
     it("uses LIGHTWEIGHT_MODEL for efficiency", async () => {
       mockGenerateText.mockResolvedValue({
         text: "Test Title",
       } as any);

       await generateChatTitle("Test input");

       expect(mockGenerateText).toHaveBeenCalledWith(
         expect.objectContaining({
-          model: expect.stringContaining("gpt-4o-mini"),
+          model: LIGHTWEIGHT_MODEL,
         }),
       );
     });
lib/chats/createChatHandler.ts (2)

53-61: Consider adding debug logging for title generation failures.

The silent catch is appropriate for graceful degradation, but completely swallowing errors makes it difficult to diagnose issues in production. Consider adding a debug-level log:

♻️ Suggested improvement
     if (firstMessage) {
       try {
         topic = await generateChatTitle(firstMessage);
-      } catch {
-        // Silently fall back to null topic on generation failure
+      } catch (error) {
+        // Fall back to null topic on generation failure
+        console.debug("[DEBUG] generateChatTitle failed, using null topic:", error);
       }
     }

55-57: Empty firstMessage will still attempt title generation.

When firstMessage is an empty string (""), the truthy check passes and generateChatTitle("") is called. This works (per tests), but wastes an LLM call. Consider checking for non-empty strings:

♻️ Suggested improvement
-    if (firstMessage) {
+    if (firstMessage?.trim()) {
lib/chat/__tests__/handleChatStream.test.ts (1)

35-46: Consider using a more accurate mock type.

The helper creates a mock typed as Request but is cast to any when passed to handleChatStream. Consider typing it as a partial NextRequest mock for better type safety in tests, or add a comment explaining the intentional simplification.

lib/chat/__tests__/handleChatGenerate.test.ts (2)

32-44: Consider typing the mock as NextRequest for clarity.

The helper returns Request but handleChatGenerate expects NextRequest, requiring as any casts at each call site. Aligning the return type improves readability.

✨ Suggested improvement
+import type { NextRequest } from "next/server";

 // Helper to create mock NextRequest
 function createMockRequest(
   body: unknown,
   headers: Record<string, string> = {},
-): Request {
+): NextRequest {
   return {
     json: () => Promise.resolve(body),
     headers: {
       get: (key: string) => headers[key.toLowerCase()] || null,
       has: (key: string) => key.toLowerCase() in headers,
     },
-  } as unknown as Request;
+  } as unknown as NextRequest;
 }

Then update call sites to remove as any:

-      const result = await handleChatGenerate(request as any);
+      const result = await handleChatGenerate(request);

294-331: Add assertion for validateOverrideAccountId call and consider a 403 test case.

The test verifies the downstream effect (setupChatRequest receives the override accountId) but doesn't assert that validateOverrideAccountId was called with correct parameters. Additionally, the 403 "Access denied" path from validateOverrideAccountId isn't tested.

✨ Suggested improvements

Add assertion in existing test:

       await handleChatGenerate(request as any);

+      expect(mockValidateOverrideAccountId).toHaveBeenCalledWith({
+        apiKey: "org-api-key",
+        targetAccountId: "target-account-456",
+      });
       expect(mockSetupChatRequest).toHaveBeenCalledWith(

Add negative test case:

    it("returns 403 when org API key lacks access to target account", async () => {
      mockGetApiKeyAccountId.mockResolvedValue("org-account-123");
      mockValidateOverrideAccountId.mockResolvedValue(
        NextResponse.json(
          { status: "error", message: "Access denied to specified accountId" },
          { status: 403 },
        ),
      );

      const request = createMockRequest(
        { prompt: "Hello", accountId: "unauthorized-account" },
        { "x-api-key": "org-api-key" },
      );

      const result = await handleChatGenerate(request as any);

      expect(result.status).toBe(403);
      const json = await result.json();
      expect(json.message).toBe("Access denied to specified accountId");
    });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffe29f5 and 678445e.

📒 Files selected for processing (12)
  • app/api/chat/generate/route.ts
  • app/api/chat/route.ts
  • lib/chat/__tests__/handleChatGenerate.test.ts
  • lib/chat/__tests__/handleChatStream.test.ts
  • lib/chat/handleChatGenerate.ts
  • lib/chat/handleChatStream.ts
  • lib/chats/__tests__/createChatHandler.test.ts
  • lib/chats/__tests__/generateChatTitle.test.ts
  • lib/chats/__tests__/validateCreateChatBody.test.ts
  • lib/chats/createChatHandler.ts
  • lib/chats/generateChatTitle.ts
  • lib/chats/validateCreateChatBody.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Apply Single Responsibility Principle (SRP) - one exported function per file
Apply DRY principle (Don't Repeat Yourself) - extract shared logic into reusable utilities

Files:

  • lib/chats/generateChatTitle.ts
  • lib/chats/__tests__/generateChatTitle.test.ts
  • lib/chats/__tests__/validateCreateChatBody.test.ts
  • lib/chat/handleChatStream.ts
  • app/api/chat/generate/route.ts
  • lib/chat/handleChatGenerate.ts
  • app/api/chat/route.ts
  • lib/chat/__tests__/handleChatStream.test.ts
  • lib/chats/createChatHandler.ts
  • lib/chat/__tests__/handleChatGenerate.test.ts
  • lib/chats/__tests__/createChatHandler.test.ts
  • lib/chats/validateCreateChatBody.ts
!(lib/supabase)/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER import @/lib/supabase/serverClient outside of lib/supabase/ directory

Files:

  • lib/chats/generateChatTitle.ts
  • lib/chats/__tests__/generateChatTitle.test.ts
  • lib/chats/__tests__/validateCreateChatBody.test.ts
  • lib/chat/handleChatStream.ts
  • app/api/chat/generate/route.ts
  • lib/chat/handleChatGenerate.ts
  • app/api/chat/route.ts
  • lib/chat/__tests__/handleChatStream.test.ts
  • lib/chats/createChatHandler.ts
  • lib/chat/__tests__/handleChatGenerate.test.ts
  • lib/chats/__tests__/createChatHandler.test.ts
  • lib/chats/validateCreateChatBody.ts
lib/@(auth|chats|emails|ai|x402|trigger)/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When needing database access in domain folders (lib/auth/, lib/chats/, etc.), first check if a function exists in lib/supabase/[table_name]/, create it if not, then import and use it

Files:

  • lib/chats/generateChatTitle.ts
  • lib/chats/__tests__/generateChatTitle.test.ts
  • lib/chats/__tests__/validateCreateChatBody.test.ts
  • lib/chats/createChatHandler.ts
  • lib/chats/__tests__/createChatHandler.test.ts
  • lib/chats/validateCreateChatBody.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

app/api/**/*.{ts,tsx}: All API routes should have JSDoc comments
All API endpoints should use a validate function for input parsing with Zod for schema validation

Files:

  • app/api/chat/generate/route.ts
  • app/api/chat/route.ts
🧠 Learnings (2)
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to app/api/**/*.{ts,tsx} : All API routes should have JSDoc comments

Applied to files:

  • app/api/chat/route.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/@(auth|chats|emails|ai|x402|trigger)/**/*.{ts,tsx} : When needing database access in domain folders (lib/auth/, lib/chats/, etc.), first check if a function exists in `lib/supabase/[table_name]/`, create it if not, then import and use it

Applied to files:

  • lib/chats/__tests__/createChatHandler.test.ts
🧬 Code graph analysis (11)
lib/chats/generateChatTitle.ts (1)
lib/const.ts (1)
  • LIGHTWEIGHT_MODEL (13-13)
lib/chats/__tests__/generateChatTitle.test.ts (1)
lib/chats/generateChatTitle.ts (1)
  • generateChatTitle (11-21)
lib/chats/__tests__/validateCreateChatBody.test.ts (1)
lib/chats/validateCreateChatBody.ts (1)
  • validateCreateChatBody (20-39)
lib/chat/handleChatStream.ts (4)
lib/chat/validateChatRequest.ts (1)
  • validateChatRequest (55-117)
lib/chat/setupChatRequest.ts (1)
  • setupChatRequest (23-63)
lib/uuid/generateUUID.ts (1)
  • generateUUID (8-20)
lib/networking/getCorsHeaders.ts (1)
  • getCorsHeaders (6-12)
app/api/chat/generate/route.ts (2)
lib/networking/getCorsHeaders.ts (1)
  • getCorsHeaders (6-12)
lib/chat/handleChatGenerate.ts (1)
  • handleChatGenerate (19-65)
lib/chat/handleChatGenerate.ts (3)
lib/chat/validateChatRequest.ts (1)
  • validateChatRequest (55-117)
lib/chat/setupChatRequest.ts (1)
  • setupChatRequest (23-63)
lib/networking/getCorsHeaders.ts (1)
  • getCorsHeaders (6-12)
app/api/chat/route.ts (2)
lib/networking/getCorsHeaders.ts (1)
  • getCorsHeaders (6-12)
lib/chat/handleChatStream.ts (1)
  • handleChatStream (19-63)
lib/chat/__tests__/handleChatStream.test.ts (4)
lib/auth/getApiKeyAccountId.ts (1)
  • getApiKeyAccountId (14-77)
lib/accounts/validateOverrideAccountId.ts (1)
  • validateOverrideAccountId (28-78)
lib/chat/setupChatRequest.ts (1)
  • setupChatRequest (23-63)
lib/chat/handleChatStream.ts (1)
  • handleChatStream (19-63)
lib/chats/createChatHandler.ts (2)
lib/chats/generateChatTitle.ts (1)
  • generateChatTitle (11-21)
lib/supabase/rooms/insertRoom.ts (1)
  • insertRoom (8-14)
lib/chat/__tests__/handleChatGenerate.test.ts (4)
lib/auth/getApiKeyAccountId.ts (1)
  • getApiKeyAccountId (14-77)
lib/accounts/validateOverrideAccountId.ts (1)
  • validateOverrideAccountId (28-78)
lib/chat/setupChatRequest.ts (1)
  • setupChatRequest (23-63)
lib/chat/handleChatGenerate.ts (1)
  • handleChatGenerate (19-65)
lib/chats/__tests__/createChatHandler.test.ts (5)
lib/auth/getApiKeyAccountId.ts (1)
  • getApiKeyAccountId (14-77)
lib/networking/safeParseJson.ts (1)
  • safeParseJson (10-16)
lib/chats/generateChatTitle.ts (1)
  • generateChatTitle (11-21)
lib/supabase/rooms/insertRoom.ts (1)
  • insertRoom (8-14)
lib/chats/createChatHandler.ts (1)
  • createChatHandler (21-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (19)
lib/chats/validateCreateChatBody.ts (1)

9-9: LGTM - optional firstMessage field added correctly.

The schema change is clean and properly typed. Consider whether a maximum length constraint (e.g., .max(1000)) would be beneficial to prevent excessively long messages from being passed to the title generation prompt, though this may be intentionally left unconstrained.

lib/chats/__tests__/validateCreateChatBody.test.ts (1)

105-136: Tests cover the new firstMessage field comprehensively.

The test suite follows the established patterns in this file and covers the key scenarios: valid string, missing (undefined), and empty string. Good coverage for the optional field behavior.

lib/chats/__tests__/generateChatTitle.test.ts (1)

174-195: Good coverage of edge cases.

The empty input and very long input tests are valuable for documenting expected behavior. The tests appropriately verify that the function delegates to the LLM and returns whatever it produces.

lib/chats/createChatHandler.ts (1)

37-37: Integration of firstMessage and topic generation looks correct.

The flow properly extracts firstMessage from validated input, generates a topic when available, and passes it to insertRoom. The fallback to null on failure maintains backward compatibility.

Also applies to: 63-68

lib/chats/__tests__/createChatHandler.test.ts (4)

9-9: LGTM!

The import and mock setup for generateChatTitle follows the existing patterns in this test file and is correctly configured.

Also applies to: 36-38


178-210: LGTM!

This test correctly validates the happy path for title generation, ensuring generateChatTitle is called with the provided firstMessage and the resulting title is passed to insertRoom.


212-238: LGTM!

The test correctly verifies that generateChatTitle is not invoked when firstMessage is absent and that topic defaults to null.


240-271: LGTM!

Good coverage of the error handling path—verifies that when generateChatTitle throws, the handler gracefully falls back to null topic without failing the request.

lib/chat/handleChatStream.ts (2)

19-63: LGTM on the overall handler structure.

The function correctly:

  • Validates the request before processing
  • Sets up chat configuration via setupChatRequest
  • Implements dual-layer error handling (streaming onError + global try/catch)
  • Returns appropriate status codes with CORS headers on errors

1-6: No action required. The import style for generateUUID is correct.

Line 6 uses a default import, which is valid because lib/uuid/generateUUID.ts exports the function both as a named export and as a default export (line 22: export default generateUUID;). Both import styles will work without runtime errors.

Likely an incorrect or invalid review comment.

lib/chat/handleChatGenerate.ts (1)

19-65: LGTM on the handler structure and error handling.

The function follows the same well-structured pattern as handleChatStream:

  • Request validation with early return
  • Chat configuration setup
  • Consistent error handling with CORS headers
  • Comprehensive JSDoc documentation
lib/chat/__tests__/handleChatStream.test.ts (1)

91-133: Good test coverage for the streaming path.

The streaming tests properly verify:

  • setupChatRequest is called with correct parameters
  • createUIMessageStream and createUIMessageStreamResponse are invoked
  • The mock response is returned correctly

Consider adding a test for when agent.stream() throws an error (inside the execute callback) to verify the onError handler behavior, though this is optional.

app/api/chat/route.ts (1)

1-40: LGTM!

The route file follows best practices:

  • JSDoc comments on both handlers (per coding guidelines)
  • CORS preflight handling via OPTIONS
  • Clean delegation to handleChatStream for business logic
  • Comprehensive request/response documentation
app/api/chat/generate/route.ts (1)

1-48: LGTM!

The route file is well-structured and consistent with app/api/chat/route.ts:

  • Comprehensive JSDoc documentation on both handlers
  • Includes response body documentation (appropriate for non-streaming JSON endpoint)
  • Clean delegation pattern to handleChatGenerate
  • CORS preflight handling
lib/chat/__tests__/handleChatGenerate.test.ts (5)

1-30: LGTM!

The mock setup follows Vitest best practices by defining mocks before imports, ensuring proper hoisting behavior. The use of vi.mocked() for typed mock instances is appropriate.


46-54: LGTM!

Proper test isolation with clearAllMocks in beforeEach and restoreAllMocks in afterEach.


55-87: LGTM!

Validation tests cover the essential error paths: missing input data (400) and missing authentication (401). The mock setup correctly simulates each scenario.


89-245: LGTM!

Comprehensive test coverage for text generation scenarios including basic requests, message arrays, optional parameters, and extended response fields (reasoningText, sources). The assertions properly verify both response structure and mock interactions.


247-292: LGTM!

Error handling tests properly verify 500 responses when either setupChatRequest or generateText throws, ensuring the handler gracefully catches and reports errors.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +41 to +45
response: {
messages: result.response.messages,
headers: result.response.headers,
body: result.response.body,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential serialization issue with response.headers and response.body.

The result.response.headers is likely a Headers object which doesn't serialize to JSON properly (it becomes {}). Similarly, response.body may be a ReadableStream or contain non-serializable data. This could result in empty or unexpected values in the API response.

Consider explicitly converting headers to a plain object or omitting non-serializable fields:

Proposed fix
         response: {
           messages: result.response.messages,
-          headers: result.response.headers,
-          body: result.response.body,
+          headers: result.response.headers
+            ? Object.fromEntries(result.response.headers.entries())
+            : undefined,
         },
🤖 Prompt for AI Agents
In `@lib/chat/handleChatGenerate.ts` around lines 41 - 45, The API is returning
non-serializable data: result.response.headers (likely a Headers instance) and
result.response.body (possibly a ReadableStream) will not JSON-serialize
correctly; update the response construction in handleChatGenerate (the response:
{ messages, headers, body } block) to convert headers to a plain object (e.g.,
iterate result.response.headers and build a key/value map) and either
read/serialize the body to a string/JSON (if small and safe) or omit/replace it
with a safe indicator (e.g., null or a size/type descriptor) to ensure the
returned payload is fully serializable.

Use NextResponse instead of Response in the return type so TypeScript
properly narrows the union type when checking instanceof NextResponse.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use specific provider option types (AnthropicProviderOptions,
GoogleGenerativeAIProviderOptions, OpenAIResponsesProviderOptions)
instead of Record<string, unknown> for compatibility with generateText.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
sweetmantech and others added 2 commits January 14, 2026 20:21
Add @ai-sdk/anthropic, @ai-sdk/google, and @ai-sdk/openai for
provider-specific type definitions used in ChatConfig.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add credit calculation and deduction logic for chat usage:
- getAvailableModels: fetches models from Vercel AI Gateway
- getModel: finds model by ID from available models
- isEmbedModel: filters embed models by output price
- getCreditUsage: calculates USD cost from token usage
- handleChatCredits: orchestrates credit deduction after chat

Includes 22 unit tests (8 for handleChatCredits, 7 for getCreditUsage,
4 for getModel, 3 for getAvailableModels).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add @ai-sdk/gateway for getAvailableModels functionality.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add post-completion handler for chat messages:
- handleChatCompletion: orchestrates post-chat tasks
- selectRoom: fetches room by ID from Supabase
- upsertMemory: stores messages to memories table
- extractSendEmailResults: parses email tool outputs
- handleSendEmailToolOutputs: links emails to memories
- sendErrorNotification: sends errors to Telegram
- serializeError: converts errors to JSON format

Includes 14 unit tests covering message storage, new conversation
handling, email tool outputs, account email lookup, and error handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive test coverage for the chat agent system:
- getGeneralAgent: 23 tests covering agent creation, account lookups, artist context
- getSystemPrompt: 29 tests for context values, user/artist sections, knowledge base
- extractImageUrlsFromMessages: 19 tests for media type filtering and edge cases
- buildSystemPromptWithImages: 12 tests for output formatting
- getKnowledgeBaseText: 21 tests for file type filtering and content fetching

Total: 104 new unit tests bringing test count to 317

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@lib/chat/handleChatCompletion.ts`:
- Around line 79-83: The code unconditionally indexes
responseMessages[responseMessages.length - 1] which will throw if
responseMessages is empty; guard this by checking responseMessages is non-empty
before calling upsertMemory (e.g., if responseMessages.length > 0) or by
retrieving the last message into a variable (e.g., lastMessage =
responseMessages.at(-1)) and returning/skip-upsert when lastMessage is
undefined, then call upsertMemory with lastMessage.id, roomId, and
filterMessageContentForMemories(lastMessage) only when lastMessage exists;
update the block around upsertMemory, responseMessages,
filterMessageContentForMemories, and roomId accordingly.

In `@lib/emails/extractSendEmailResults.ts`:
- Around line 34-38: Wrap the JSON.parse call for output.content[0].text inside
a try/catch in extractSendEmailResults (where parsed is created) so malformed
JSON doesn't throw and stop processing; on parse error, log or record the
failure (including the offending text or error) and continue to the next
iteration instead of letting the exception propagate, then only use
parsed.data?.id when parsing succeeds.

In `@lib/telegram/sendErrorNotification.ts`:
- Around line 19-26: The formatErrorMessage function builds a Markdown string
from user/runtime values (path, error.name, error.message) which can contain
Markdown metacharacters and break formatting; add a helper (e.g. escapeMarkdown)
that escapes characters like *, _, `, [, ] and apply it to path, error.name and
error.message inside formatErrorMessage (or switch to parse_mode: "HTML" and
HTML-escape those values) so the Telegram message formatting cannot be injected
or corrupted.

In `@package.json`:
- Around line 18-22: The package.json lists non-existent `@ai-sdk/`* 3.x versions;
update the dependency versions to the latest published ones to avoid install
failures—specifically change "@ai-sdk/anthropic" to 2.0.17, "@ai-sdk/gateway" to
1.0.19, "@ai-sdk/google" to 2.0.11, and "@ai-sdk/openai" to 2.0.24 in the
dependencies block so npm install can succeed.
♻️ Duplicate comments (1)
lib/credits/getCreditUsage.ts (1)

28-31: Truthiness check incorrectly treats 0 as missing tokens.

The condition !inputTokens || !outputTokens evaluates 0 as falsy, causing the function to return 0 instead of calculating the actual cost when either token count is legitimately 0. Use explicit null/undefined checks instead.

🐛 Proposed fix
-    if (!inputTokens || !outputTokens) {
+    if (inputTokens == null || outputTokens == null) {
🧹 Nitpick comments (11)
lib/supabase/rooms/selectRoom.ts (1)

17-19: Consider adding error logging for debuggability.

Silently returning null on all errors (connection failures, permission issues, row not found) makes it difficult to distinguish between "room doesn't exist" and actual failures in production.

♻️ Suggested improvement with logging
   if (error) {
+    if (error.code !== "PGRST116") {
+      // Log unexpected errors (PGRST116 = row not found, which is expected)
+      console.error("selectRoom error:", error.message);
+    }
     return null;
   }

Alternatively, if you want to keep the silent behavior for "not found" but surface actual errors, you could throw on unexpected error codes.

lib/files/__tests__/getKnowledgeBaseText.test.ts (1)

198-219: Consider verifying the exact separator format for multiple files.

The test verifies both contents exist but doesn't explicitly assert the \n\n separator between files. A more precise assertion could ensure the exact format is preserved.

🔧 Optional: Add explicit separator verification
       const result = await getKnowledgeBaseText(knowledges);
 
       expect(result).toContain("--- a.txt ---");
       expect(result).toContain("--- b.txt ---");
       expect(result).toContain("Content A");
       expect(result).toContain("Content B");
+      expect(result).toMatch(/Content A\n\n--- b\.txt ---/);
     });
lib/prompts/__tests__/getSystemPrompt.test.ts (1)

86-182: Consider using proper type imports instead of as any casts.

The repeated as any casts for accountWithDetails bypass TypeScript's type checking, which could hide issues if the AccountWithDetails interface changes in the future. Consider importing the type and using Partial<> or creating a test helper.

♻️ Suggested approach
import type { AccountWithDetails } from "@/types/account"; // adjust import path

// Option 1: Use Partial type
const result = getSystemPrompt({
  accountId: "acc-1",
  accountWithDetails: {
    id: "acc-1",
    name: "John Doe",
    email: "john@example.com",
  } as Partial<AccountWithDetails> as AccountWithDetails,
});

// Option 2: Create a test helper
function createTestAccountWithDetails(
  overrides: Partial<AccountWithDetails>
): AccountWithDetails {
  return {
    id: "test-id",
    ...overrides,
  } as AccountWithDetails;
}
lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts (2)

3-3: Unused import: stepCountIs.

The stepCountIs function is imported but never used in the test file. Consider removing it or using it to verify the actual stop condition behavior.

♻️ Suggested fix
-import { ToolLoopAgent, stepCountIs } from "ai";
+import { ToolLoopAgent } from "ai";

132-143: Test assertion could be more precise.

The test name claims "sets stopWhen to stepCountIs(111)" but only verifies that stopWhen is defined and is a function. This doesn't verify the actual step count value.

Consider invoking the stop condition to verify it returns the expected boolean at the correct step count:

♻️ Suggested enhancement
     it("sets stopWhen to stepCountIs(111)", async () => {
       const body: ChatRequestBody = {
         accountId: "account-123",
         messages: [{ id: "1", role: "user", content: "Hello" }],
       };

       const result = await getGeneralAgent(body);

       expect(result.stopWhen).toBeDefined();
-      // stepCountIs returns a function, verify it's the expected type
       expect(typeof result.stopWhen).toBe("function");
+      // Verify the stop condition triggers at step 111
+      const mockState = { steps: [] as unknown[] };
+      mockState.steps.length = 110;
+      expect(result.stopWhen!(mockState as any)).toBe(false);
+      mockState.steps.length = 111;
+      expect(result.stopWhen!(mockState as any)).toBe(true);
     });
lib/ai/getAvailableModels.ts (1)

15-17: Consider adding error logging for observability.

The catch block silently returns an empty array, which makes it impossible to distinguish between "gateway returned no models" and "an error occurred." For consistency with getModel.ts (which logs errors), consider adding logging here to aid debugging.

♻️ Suggested improvement
-  } catch {
+  } catch (error) {
+    console.error("Failed to fetch available models from gateway:", error);
     return [];
   }
lib/credits/__tests__/getCreditUsage.test.ts (1)

110-127: Test passes for the wrong reason due to the truthiness bug.

This test expects 0 when both tokens are 0, and it currently passes. However, it passes because the implementation incorrectly treats 0 as falsy in the !inputTokens || !outputTokens check, not because 0 * price = 0. Once the truthiness bug in getCreditUsage.ts is fixed to use == null checks, this test will correctly return 0 from the actual calculation (0 * price + 0 * price = 0).

The test expectation is correct, but consider adding a comment clarifying that zero tokens should yield zero cost through calculation, not validation failure.

lib/supabase/memories/upsertMemory.ts (1)

16-31: JSDoc and return type don't match actual behavior.

The JSDoc states "returns null if the upsert fails" and the return type is Promise<Memory | null>, but the function throws on error rather than returning null. Since .single() returns non-null data when successful, consider:

  1. Updating the return type to Promise<Memory> and removing the "or null" from JSDoc, or
  2. Catching errors and returning null (less recommended as it hides failures)
Proposed fix (option 1 - accurate types)
 /**
  * Upserts a memory into the memories table.
  * If a memory with the same ID exists, it will be updated.
  *
  * `@param` params - The parameters for the memory
  * `@param` params.id - The ID of the memory
  * `@param` params.room_id - The ID of the room
  * `@param` params.content - The content of the memory
- * `@returns` The upserted memory, or null if the upsert fails
+ * `@returns` The upserted memory
+ * `@throws` Error if the upsert fails
  */
-export default async function upsertMemory(params: UpsertMemoryParams): Promise<Memory | null> {
+export default async function upsertMemory(params: UpsertMemoryParams): Promise<Memory> {
   const { data, error } = await supabase
     .from("memories")
     .upsert(params, { onConflict: "id" })
     .select()
     .single();

   if (error) {
     console.error("Error upserting memory:", error);
     throw error;
   }

   return data;
 }
lib/telegram/sendErrorNotification.ts (1)

5-11: Unused messages field in ErrorContext.

The messages field is declared in the interface but never used in formatErrorMessage. If this is intended for future use, consider removing it until needed to avoid confusion.

Additionally, be aware that the email field may contain PII that will be sent to Telegram. Ensure this aligns with your privacy/compliance requirements.

♻️ Suggested change if messages is not needed
 export interface ErrorContext {
   email?: string;
   roomId?: string;
-  messages?: UIMessage[];
   path: string;
   error: SerializedError;
 }

And remove the unused import:

-import type { UIMessage } from "ai";
 import { sendMessage } from "./sendMessage";
lib/chat/handleChatCompletion.ts (1)

87-94: Fire-and-forget notification pattern.

The sendErrorNotification call is not awaited. While this is intentional for non-blocking behavior (and the function has internal error handling), consider adding void operator for clarity that this is intentional:

   } catch (error) {
-    sendErrorNotification({
+    void sendErrorNotification({
       ...body,
       path: "/api/chat",
       error: serializeError(error),
     });
     console.error("Failed to save chat", error);
   }
lib/chat/__tests__/handleChatCompletion.test.ts (1)

264-310: Missing test for empty responseMessages array.

Given the potential crash when responseMessages is empty (flagged in handleChatCompletion.ts), consider adding a test case to verify the expected behavior:

🧪 Suggested test case
it("handles empty responseMessages array gracefully", async () => {
  const body = createMockBody();
  const responseMessages: UIMessage[] = [];

  // Should not throw
  await expect(handleChatCompletion(body, responseMessages)).resolves.toBeUndefined();
  
  // Should still store user message
  expect(mockUpsertMemory).toHaveBeenCalledTimes(1);
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebc38e and dd3a150.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts
  • lib/ai/__tests__/getAvailableModels.test.ts
  • lib/ai/__tests__/getModel.test.ts
  • lib/ai/getAvailableModels.ts
  • lib/ai/getModel.ts
  • lib/ai/isEmbedModel.ts
  • lib/chat/__tests__/buildSystemPromptWithImages.test.ts
  • lib/chat/__tests__/handleChatCompletion.test.ts
  • lib/chat/handleChatCompletion.ts
  • lib/chat/validateMessages.ts
  • lib/credits/__tests__/getCreditUsage.test.ts
  • lib/credits/__tests__/handleChatCredits.test.ts
  • lib/credits/getCreditUsage.ts
  • lib/credits/handleChatCredits.ts
  • lib/emails/extractSendEmailResults.ts
  • lib/emails/handleSendEmailToolOutputs.ts
  • lib/errors/serializeError.ts
  • lib/files/__tests__/getKnowledgeBaseText.test.ts
  • lib/messages/__tests__/extractImageUrlsFromMessages.test.ts
  • lib/prompts/__tests__/getSystemPrompt.test.ts
  • lib/supabase/memories/upsertMemory.ts
  • lib/supabase/rooms/selectRoom.ts
  • lib/telegram/sendErrorNotification.ts
  • package.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Apply Single Responsibility Principle (SRP) - one exported function per file
Apply DRY principle (Don't Repeat Yourself) - extract shared logic into reusable utilities

Files:

  • lib/files/__tests__/getKnowledgeBaseText.test.ts
  • lib/ai/isEmbedModel.ts
  • lib/credits/__tests__/getCreditUsage.test.ts
  • lib/credits/handleChatCredits.ts
  • lib/supabase/memories/upsertMemory.ts
  • lib/credits/__tests__/handleChatCredits.test.ts
  • lib/chat/handleChatCompletion.ts
  • lib/ai/getModel.ts
  • lib/credits/getCreditUsage.ts
  • lib/telegram/sendErrorNotification.ts
  • lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts
  • lib/emails/handleSendEmailToolOutputs.ts
  • lib/emails/extractSendEmailResults.ts
  • lib/messages/__tests__/extractImageUrlsFromMessages.test.ts
  • lib/ai/__tests__/getAvailableModels.test.ts
  • lib/ai/__tests__/getModel.test.ts
  • lib/errors/serializeError.ts
  • lib/chat/__tests__/buildSystemPromptWithImages.test.ts
  • lib/supabase/rooms/selectRoom.ts
  • lib/ai/getAvailableModels.ts
  • lib/chat/validateMessages.ts
  • lib/chat/__tests__/handleChatCompletion.test.ts
  • lib/prompts/__tests__/getSystemPrompt.test.ts
!(lib/supabase)/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER import @/lib/supabase/serverClient outside of lib/supabase/ directory

Files:

  • lib/files/__tests__/getKnowledgeBaseText.test.ts
  • lib/ai/isEmbedModel.ts
  • lib/credits/__tests__/getCreditUsage.test.ts
  • lib/credits/handleChatCredits.ts
  • lib/supabase/memories/upsertMemory.ts
  • lib/credits/__tests__/handleChatCredits.test.ts
  • lib/chat/handleChatCompletion.ts
  • lib/ai/getModel.ts
  • lib/credits/getCreditUsage.ts
  • lib/telegram/sendErrorNotification.ts
  • lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts
  • lib/emails/handleSendEmailToolOutputs.ts
  • lib/emails/extractSendEmailResults.ts
  • lib/messages/__tests__/extractImageUrlsFromMessages.test.ts
  • lib/ai/__tests__/getAvailableModels.test.ts
  • lib/ai/__tests__/getModel.test.ts
  • lib/errors/serializeError.ts
  • lib/chat/__tests__/buildSystemPromptWithImages.test.ts
  • lib/supabase/rooms/selectRoom.ts
  • package.json
  • lib/ai/getAvailableModels.ts
  • lib/chat/validateMessages.ts
  • lib/chat/__tests__/handleChatCompletion.test.ts
  • lib/prompts/__tests__/getSystemPrompt.test.ts
lib/@(auth|chats|emails|ai|x402|trigger)/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When needing database access in domain folders (lib/auth/, lib/chats/, etc.), first check if a function exists in lib/supabase/[table_name]/, create it if not, then import and use it

Files:

  • lib/ai/isEmbedModel.ts
  • lib/ai/getModel.ts
  • lib/emails/handleSendEmailToolOutputs.ts
  • lib/emails/extractSendEmailResults.ts
  • lib/ai/__tests__/getAvailableModels.test.ts
  • lib/ai/__tests__/getModel.test.ts
  • lib/ai/getAvailableModels.ts
lib/supabase/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

lib/supabase/**/*.{ts,tsx}: All Supabase database calls must be in lib/supabase/[table_name]/[function].ts
Supabase select functions should be named select[TableName].ts
Supabase insert functions should be named insert[TableName].ts
Supabase update functions should be named update[TableName].ts
Supabase delete functions should be named delete[TableName].ts
Complex Supabase queries with joins should be named get[Descriptive].ts

Files:

  • lib/supabase/memories/upsertMemory.ts
  • lib/supabase/rooms/selectRoom.ts
🧠 Learnings (5)
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/supabase/**/*.{ts,tsx} : Supabase insert functions should be named `insert[TableName].ts`

Applied to files:

  • lib/supabase/memories/upsertMemory.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/supabase/**/*.{ts,tsx} : Supabase update functions should be named `update[TableName].ts`

Applied to files:

  • lib/supabase/memories/upsertMemory.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/@(auth|chats|emails|ai|x402|trigger)/**/*.{ts,tsx} : When needing database access in domain folders (lib/auth/, lib/chats/, etc.), first check if a function exists in `lib/supabase/[table_name]/`, create it if not, then import and use it

Applied to files:

  • lib/supabase/memories/upsertMemory.ts
  • lib/supabase/rooms/selectRoom.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/supabase/**/*.{ts,tsx} : All Supabase database calls must be in `lib/supabase/[table_name]/[function].ts`

Applied to files:

  • lib/supabase/memories/upsertMemory.ts
📚 Learning: 2026-01-14T22:12:03.852Z
Learnt from: CR
Repo: Recoupable-com/Recoup-API PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-14T22:12:03.852Z
Learning: Applies to lib/supabase/**/*.{ts,tsx} : Supabase select functions should be named `select[TableName].ts`

Applied to files:

  • lib/supabase/rooms/selectRoom.ts
🧬 Code graph analysis (18)
lib/files/__tests__/getKnowledgeBaseText.test.ts (1)
lib/files/getKnowledgeBaseText.ts (1)
  • getKnowledgeBaseText (8-40)
lib/credits/__tests__/getCreditUsage.test.ts (2)
lib/ai/getModel.ts (1)
  • getModel (9-19)
lib/credits/getCreditUsage.ts (1)
  • getCreditUsage (10-47)
lib/credits/handleChatCredits.ts (2)
lib/credits/getCreditUsage.ts (1)
  • getCreditUsage (10-47)
lib/credits/deductCredits.ts (1)
  • deductCredits (24-56)
lib/supabase/memories/upsertMemory.ts (1)
types/database.types.ts (1)
  • Tables (3819-3844)
lib/credits/__tests__/handleChatCredits.test.ts (3)
lib/credits/getCreditUsage.ts (1)
  • getCreditUsage (10-47)
lib/credits/deductCredits.ts (1)
  • deductCredits (24-56)
lib/credits/handleChatCredits.ts (1)
  • handleChatCredits (18-43)
lib/chat/handleChatCompletion.ts (10)
lib/chat/validateChatRequest.ts (1)
  • ChatRequestBody (41-43)
lib/supabase/account_emails/selectAccountEmails.ts (1)
  • selectAccountEmails (12-48)
lib/chat/validateMessages.ts (1)
  • validateMessages (10-21)
lib/supabase/rooms/selectRoom.ts (1)
  • selectRoom (12-22)
lib/supabase/rooms/insertRoom.ts (1)
  • insertRoom (8-14)
lib/telegram/sendNewConversationNotification.ts (1)
  • sendNewConversationNotification (12-33)
lib/supabase/memories/upsertMemory.ts (1)
  • upsertMemory (18-31)
lib/emails/handleSendEmailToolOutputs.ts (1)
  • handleSendEmailToolOutputs (11-24)
lib/telegram/sendErrorNotification.ts (1)
  • sendErrorNotification (48-55)
lib/errors/serializeError.ts (1)
  • serializeError (17-29)
lib/ai/getModel.ts (1)
lib/ai/getAvailableModels.ts (1)
  • getAvailableModels (8-18)
lib/credits/getCreditUsage.ts (1)
lib/ai/getModel.ts (1)
  • getModel (9-19)
lib/telegram/sendErrorNotification.ts (2)
lib/errors/serializeError.ts (1)
  • SerializedError (4-8)
lib/telegram/sendMessage.ts (1)
  • sendMessage (12-23)
lib/emails/handleSendEmailToolOutputs.ts (2)
lib/emails/extractSendEmailResults.ts (1)
  • extractSendEmailResults (15-47)
lib/supabase/memory_emails/insertMemoryEmail.ts (1)
  • insertMemoryEmail (19-30)
lib/messages/__tests__/extractImageUrlsFromMessages.test.ts (1)
lib/messages/extractImageUrlsFromMessages.ts (1)
  • extractImageUrlsFromMessages (9-28)
lib/ai/__tests__/getAvailableModels.test.ts (1)
lib/ai/getAvailableModels.ts (1)
  • getAvailableModels (8-18)
lib/ai/__tests__/getModel.test.ts (2)
lib/ai/getAvailableModels.ts (1)
  • getAvailableModels (8-18)
lib/ai/getModel.ts (1)
  • getModel (9-19)
lib/chat/__tests__/buildSystemPromptWithImages.test.ts (1)
lib/chat/buildSystemPromptWithImages.ts (1)
  • buildSystemPromptWithImages (8-14)
lib/supabase/rooms/selectRoom.ts (1)
types/database.types.ts (1)
  • Tables (3819-3844)
lib/ai/getAvailableModels.ts (1)
lib/ai/isEmbedModel.ts (1)
  • isEmbedModel (7-12)
lib/chat/__tests__/handleChatCompletion.test.ts (9)
lib/supabase/account_emails/selectAccountEmails.ts (1)
  • selectAccountEmails (12-48)
lib/supabase/rooms/selectRoom.ts (1)
  • selectRoom (12-22)
lib/supabase/rooms/insertRoom.ts (1)
  • insertRoom (8-14)
lib/supabase/memories/upsertMemory.ts (1)
  • upsertMemory (18-31)
lib/telegram/sendNewConversationNotification.ts (1)
  • sendNewConversationNotification (12-33)
lib/emails/handleSendEmailToolOutputs.ts (1)
  • handleSendEmailToolOutputs (11-24)
lib/telegram/sendErrorNotification.ts (1)
  • sendErrorNotification (48-55)
lib/chat/validateChatRequest.ts (1)
  • ChatRequestBody (41-43)
lib/chat/handleChatCompletion.ts (1)
  • handleChatCompletion (28-95)
lib/prompts/__tests__/getSystemPrompt.test.ts (1)
lib/prompts/getSystemPrompt.ts (1)
  • getSystemPrompt (18-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (37)
lib/supabase/rooms/selectRoom.ts (1)

1-4: LGTM! File structure and naming follow project conventions.

The file path (lib/supabase/rooms/selectRoom.ts), function name (selectRoom), and single exported function align with the Supabase module conventions. The type alias using Tables<"rooms"> correctly leverages the generated database types.

Based on learnings, the naming convention select[TableName].ts is correctly applied.

lib/files/__tests__/getKnowledgeBaseText.test.ts (5)

1-14: Well-structured test setup with proper mock cleanup.

The setup correctly saves/restores the original global.fetch and uses vi.clearAllMocks() in afterEach to ensure test isolation.


16-41: Input validation tests provide comprehensive coverage.

Tests cover all guard conditions in the implementation: null, undefined, empty array, non-array string, and object inputs. These align correctly with the implementation's check: !knowledges || !Array.isArray(knowledges) || knowledges.length === 0.


43-126: File type filtering tests are thorough.

Good coverage of all four allowed MIME types (text/plain, text/markdown, application/json, text/csv) and verification that non-text types (image/jpeg, application/pdf, audio/mpeg) are excluded without triggering fetch calls.


128-168: Content fetching tests cover success and failure scenarios.

Tests properly verify URL is called, and gracefully handle both failed responses (ok: false) and network errors (rejected promise). This aligns with the implementation's try-catch and res.ok check.


222-270: Edge case tests are well-designed.

Good coverage of files missing URL or type, mixed valid/invalid inputs (with correct assertion that only 1 fetch is made), and the scenario where all fetches fail returning undefined.

lib/prompts/__tests__/getSystemPrompt.test.ts (6)

1-17: LGTM!

Imports and basic functionality tests are well-structured. The tests correctly verify the function returns a non-empty string and includes expected base content.


19-67: LGTM!

Context values tests are thorough, covering both happy paths and fallback values for all context parameters. Good coverage of edge cases like empty accountId and missing optional fields.


69-84: LGTM!

Image editing instruction tests appropriately verify the presence of required guidance sections.


184-206: LGTM!

Artist context tests correctly verify inclusion, end markers, and conditional omission.


208-230: LGTM!

Knowledge base tests appropriately verify content inclusion, end markers, and conditional omission.


232-256: LGTM!

Section ordering tests use a clean approach with indexOf comparisons to verify the documented ordering of user context → artist context → knowledge base.

lib/messages/__tests__/extractImageUrlsFromMessages.test.ts (1)

1-249: Comprehensive and well-structured test suite.

The tests thoroughly cover the extractImageUrlsFromMessages function with good organization across basic functionality, media type filtering, and edge cases. The test cases align well with the implementation logic shown in the relevant code snippet.

A few observations:

  • Good coverage of the startsWith("image/") check with various image and non-image media types
  • Edge cases for missing/empty/whitespace URLs properly test the url.trim() !== "" condition
  • The mixed valid/invalid parts test (lines 228-247) is particularly valuable for ensuring correct filtering behavior
lib/chat/__tests__/buildSystemPromptWithImages.test.ts (1)

1-106: Well-designed test suite with good coverage of formatting details.

The tests effectively validate the buildSystemPromptWithImages function, covering both the happy path and edge cases. The test assertions correctly match the implementation's output format.

Particularly good coverage of:

  • Zero-indexed image numbering (Image 0, Image 1, etc.)
  • Newline separation verification via regex (line 43)
  • URL encoding preservation for query strings and special characters
lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts (1)

58-445: Comprehensive test coverage for getGeneralAgent.

The test suite is well-organized with clear separation across functional areas:

  • Basic functionality and model handling
  • Account email lookup with edge cases
  • Artist context and knowledge base integration
  • System prompt generation parameter verification
  • Image URL extraction and prompt building
  • Tools setup and agent configuration

The mock setup in beforeEach is appropriate, and vi.clearAllMocks() ensures proper test isolation. The assertions effectively verify both the function's return structure and its interactions with dependencies.

lib/ai/isEmbedModel.ts (1)

7-12: LGTM!

The implementation is clean and follows SRP. The heuristic using pricing.output === 0 to detect embed models is reasonable, and the fallback behavior (returning false when pricing is missing or output is non-numeric) is safe.

lib/ai/__tests__/getAvailableModels.test.ts (1)

14-58: LGTM!

The test suite provides good coverage of the key scenarios:

  • Filtering out embed models based on pricing.output === "0"
  • Handling empty model lists from the gateway
  • Gracefully returning an empty array on errors

The mock setup correctly places the mock definition before the import, ensuring proper hoisting behavior.

lib/ai/getModel.ts (1)

9-19: LGTM!

The implementation is straightforward and defensive. The error logging with the modelId context is helpful for debugging.

Note: The try-catch here provides an additional safety layer, though getAvailableModels() already handles its own errors internally. If this function is called frequently, consider caching the model list or checking if the gateway API supports fetching a single model by ID to avoid repeated full-list fetches.

lib/ai/__tests__/getModel.test.ts (2)

1-19: LGTM! Clean test setup with proper mock lifecycle management.

The mock setup correctly places vi.mock before the import, and the beforeEach/afterEach hooks properly clear and restore mocks between tests.


21-65: LGTM! Comprehensive test coverage for getModel.

The tests cover the key scenarios: successful retrieval, model not found, empty models array, and error handling. The assertions correctly validate the expected behavior.

lib/credits/getCreditUsage.ts (1)

1-47: Overall implementation is sound with appropriate error handling.

The function correctly handles missing models, missing pricing data, and exceptions. The SDK v2/v3 compatibility layer is a good defensive approach. Apart from the truthiness check issue noted above, the logic is correct.

lib/credits/__tests__/handleChatCredits.test.ts (3)

1-27: LGTM! Well-structured test setup with console.error suppression.

The mock setup is clean, and suppressing console.error in beforeEach prevents noise in test output while still allowing specific tests to verify logging behavior.


28-80: LGTM! Credit deduction tests comprehensively cover the rounding logic.

The tests correctly verify the formula Math.max(1, Math.round(usageCost * 100)) across various cost scenarios, including edge cases for minimum credit deduction and standard rounding.


82-151: LGTM! Skip conditions and error handling tests are thorough.

The tests correctly verify that credit deduction is skipped when appropriate and that errors are handled gracefully without breaking the chat flow, aligning with the implementation's design goals.

lib/credits/__tests__/getCreditUsage.test.ts (2)

1-20: LGTM! Standard test setup with proper mocking.

The mock configuration and lifecycle hooks follow established patterns in the codebase.


21-141: LGTM! Good coverage of edge cases and error scenarios.

The test suite covers the main calculation path and various failure modes (model not found, undefined tokens, missing pricing, API errors).

lib/credits/handleChatCredits.ts (2)

1-9: LGTM! Clean imports and well-defined interface.

The imports correctly use local module paths within lib/credits/, adhering to the coding guidelines. The interface clearly defines the expected parameters.


18-43: LGTM! Robust implementation with appropriate error handling.

The credit deduction logic correctly:

  • Validates the presence of accountId before proceeding
  • Uses Math.max(1, Math.round(usageCost * 100)) to ensure minimum 1 credit charge
  • Catches errors gracefully to avoid disrupting the chat flow

The comment on line 41 clearly documents the intentional error suppression.

lib/errors/serializeError.ts (1)

1-29: LGTM!

Clean utility for error serialization. The implementation correctly handles both Error instances and unknown error types, providing a consistent serializable shape for downstream consumers.

lib/chat/validateMessages.ts (1)

10-21: LGTM!

The validation logic correctly filters messages to those containing non-empty text parts. The use of find()?.text?.length as a filter predicate is concise and leverages JavaScript's truthy evaluation appropriately.

lib/emails/handleSendEmailToolOutputs.ts (1)

1-24: LGTM!

The implementation correctly follows the coding guidelines by importing the database function from lib/supabase/memory_emails/. The early return optimization and parallel processing via Promise.all are appropriate patterns.

lib/telegram/sendErrorNotification.ts (1)

48-55: LGTM!

The non-blocking error handling pattern is well-implemented. Catching and logging failures prevents notification issues from disrupting the main API flow.

lib/chat/handleChatCompletion.ts (2)

54-69: LGTM!

Using Promise.all for room creation and notification is appropriate since these are independent operations. The outer try/catch properly handles failures from either operation.


1-13: Imports look good.

The imports from @/lib/supabase/* correctly use the re-exported module functions rather than importing serverClient directly, which aligns with the coding guidelines.

lib/chat/__tests__/handleChatCompletion.test.ts (3)

1-56: LGTM!

The mock setup follows Vitest best practices—mocks are defined before the module import, ensuring proper hoisting. The typed mock variables provide good type safety for assertions.


57-75: LGTM!

Well-structured test helpers with sensible defaults and override capability.


77-90: LGTM!

Good use of beforeEach for resetting mocks and setting default implementations, and afterEach for cleanup with restoreAllMocks.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +19 to +26
function formatErrorMessage(context: ErrorContext): string {
const { path, error, roomId, email } = context;
const lines = [
`*Error in ${path}*`,
"",
`*Error:* ${error.name}`,
`*Message:* ${error.message}`,
];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Markdown special characters not escaped.

If error.name, error.message, or path contain Markdown special characters (*, _, `, [, ]), the message formatting will break or produce unexpected results. Consider escaping these values or using parse_mode: "HTML" which is easier to escape.

🔧 Suggested helper for escaping Markdown
function escapeMarkdown(text: string): string {
  return text.replace(/[*_`\[\]]/g, '\\$&');
}

function formatErrorMessage(context: ErrorContext): string {
  const { path, error, roomId, email } = context;
  const lines = [
    `*Error in ${escapeMarkdown(path)}*`,
    "",
    `*Error:* ${escapeMarkdown(error.name)}`,
    `*Message:* ${escapeMarkdown(error.message)}`,
  ];
  // ...rest of function
}
🤖 Prompt for AI Agents
In `@lib/telegram/sendErrorNotification.ts` around lines 19 - 26, The
formatErrorMessage function builds a Markdown string from user/runtime values
(path, error.name, error.message) which can contain Markdown metacharacters and
break formatting; add a helper (e.g. escapeMarkdown) that escapes characters
like *, _, `, [, ] and apply it to path, error.name and error.message inside
formatErrorMessage (or switch to parse_mode: "HTML" and HTML-escape those
values) so the Telegram message formatting cannot be injected or corrupted.

…gration

Add Google Sheets tools integration via Composio to setupToolsForRequest.
Now aggregates tools from MCP client and Google Sheets:
- If user is authenticated: returns full Composio Google Sheets toolkit
- If not authenticated: returns google_sheets_login tool for OAuth flow

New files:
- getGoogleSheetsTools: checks auth status and returns appropriate tools
- getLatestUserMessageText: extracts latest user message for callback URL
- googleSheetsLoginTool: initiates OAuth authentication via Composio

Added 32 unit tests for setupToolsForRequest (11), getGoogleSheetsTools (10),
and getLatestUserMessageText (11). All 349 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AI SDK v6 uses inputSchema property instead of parameters for tool
schema definition.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrates the complete toolChains orchestration framework for multi-step
tool sequences. This enables automated workflows like artist onboarding
and release report generation.

Components migrated:
- getPrepareStepResult: Orchestration engine that determines next tool
- getExecutedToolTimeline: Tracks tool execution history from steps
- TOOL_CHAINS: Registry with create_new_artist (17 steps) and
  create_release_report (5 steps) chains
- TOOL_MODEL_MAP: Maps tools to specific models (e.g., gemini-2.5-pro)
- Reference message generators for knowledge base and release reports

Integration:
- setupChatRequest now uses getPrepareStepResult in prepareStep callback
- When a chain trigger tool is executed, subsequent tools are
  automatically routed through the defined sequence

Test coverage: 38 new unit tests for toolChains framework plus
2 integration tests for setupChatRequest.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Chat endpoints now support both x-api-key and Authorization Bearer token
authentication. Uses getAuthenticatedAccountId for JWT validation via Privy.

- Updated validateChatRequest to support dual auth mechanisms
- Enforces exactly one auth method (x-api-key XOR Authorization)
- Added 3 new tests for Authorization header support
- Updated handleChatStream and handleChatGenerate tests with mock

This enables Recoup-Chat to proxy requests to recoup-api by forwarding
the Authorization header directly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rom Recoup-Chat

Add critical MCP tools needed for toolChains:
- web_deep_research: Perplexity sonar-deep-research for comprehensive research with citations
- artist_deep_research: Fetches artist socials for deep research
- chatWithPerplexity: New Perplexity API wrapper for non-streaming chat completions

These tools are required by create_new_artist and create_release_report toolChains.
Migrated with 21 unit tests total (8 + 7 + 6).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive integration tests for the chat migration:
- validateChatRequest integration (7 tests): auth validation, prompt/messages
  validation, parameter passing
- setupChatRequest integration (6 tests): account email lookup, artist context
  fetching, account details, model override
- handleChatCompletion integration (6 tests): room creation, memory storage,
  email tool outputs, error handling
- handleChatCredits integration (5 tests): credit calculation, deduction,
  zero cost handling, error recovery
- End-to-end validation flow (3 tests): complete pipeline validation

Total: 27 new integration tests, 438 tests passing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sweetmantech sweetmantech changed the base branch from main to test January 15, 2026 02:40
@sweetmantech sweetmantech merged commit 0e4f835 into test Jan 15, 2026
4 checks passed
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.

1 participant

Comments