feat: migrate chat types from Recoup-Chat to recoup-api#112
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📒 Files selected for processing (35)
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ 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. Comment |
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>
There was a problem hiding this comment.
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
RequestbutvalidateChatRequestexpectsNextRequest. While theas anycasts 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
NextRequesttype 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
📒 Files selected for processing (2)
lib/chat/__tests__/validateChatRequest.test.tslib/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.tslib/chat/__tests__/validateChatRequest.test.ts
!(lib/supabase)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
NEVER import
@/lib/supabase/serverClientoutside oflib/supabase/directory
Files:
lib/chat/validateChatRequest.tslib/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.tslib/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/serverClientoutside the supabase directory, which is correct per the guidelines.
100-116: LGTM!The message normalization logic correctly handles the conversion from
prompttomessagesformat, and the final return properly spreads the validated body with the resolvedaccountId.lib/chat/__tests__/validateChatRequest.test.ts (6)
36-99: LGTM!The schema validation tests comprehensively cover the mutual exclusivity constraint between
messagesandprompt, 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].textstructure, 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
chatRequestSchemais 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.
| const json = await request.json(); | ||
| const validationResult = chatRequestSchema.safeParse(json); |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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
questionparameter is directly interpolated into the prompt without sanitization. While this is used for title generation (low-risk), a maliciousfirstMessagecould 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. IfLIGHTWEIGHT_MODELinlib/const.tschanges, 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
firstMessageis an empty string (""), the truthy check passes andgenerateChatTitle("")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
Requestbut is cast toanywhen passed tohandleChatStream. Consider typing it as a partialNextRequestmock 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 asNextRequestfor clarity.The helper returns
RequestbuthandleChatGenerateexpectsNextRequest, requiringas anycasts 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 forvalidateOverrideAccountIdcall and consider a 403 test case.The test verifies the downstream effect (setupChatRequest receives the override accountId) but doesn't assert that
validateOverrideAccountIdwas called with correct parameters. Additionally, the 403 "Access denied" path fromvalidateOverrideAccountIdisn'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
📒 Files selected for processing (12)
app/api/chat/generate/route.tsapp/api/chat/route.tslib/chat/__tests__/handleChatGenerate.test.tslib/chat/__tests__/handleChatStream.test.tslib/chat/handleChatGenerate.tslib/chat/handleChatStream.tslib/chats/__tests__/createChatHandler.test.tslib/chats/__tests__/generateChatTitle.test.tslib/chats/__tests__/validateCreateChatBody.test.tslib/chats/createChatHandler.tslib/chats/generateChatTitle.tslib/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.tslib/chats/__tests__/generateChatTitle.test.tslib/chats/__tests__/validateCreateChatBody.test.tslib/chat/handleChatStream.tsapp/api/chat/generate/route.tslib/chat/handleChatGenerate.tsapp/api/chat/route.tslib/chat/__tests__/handleChatStream.test.tslib/chats/createChatHandler.tslib/chat/__tests__/handleChatGenerate.test.tslib/chats/__tests__/createChatHandler.test.tslib/chats/validateCreateChatBody.ts
!(lib/supabase)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
NEVER import
@/lib/supabase/serverClientoutside oflib/supabase/directory
Files:
lib/chats/generateChatTitle.tslib/chats/__tests__/generateChatTitle.test.tslib/chats/__tests__/validateCreateChatBody.test.tslib/chat/handleChatStream.tsapp/api/chat/generate/route.tslib/chat/handleChatGenerate.tsapp/api/chat/route.tslib/chat/__tests__/handleChatStream.test.tslib/chats/createChatHandler.tslib/chat/__tests__/handleChatGenerate.test.tslib/chats/__tests__/createChatHandler.test.tslib/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.tslib/chats/__tests__/generateChatTitle.test.tslib/chats/__tests__/validateCreateChatBody.test.tslib/chats/createChatHandler.tslib/chats/__tests__/createChatHandler.test.tslib/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.tsapp/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
firstMessagefrom validated input, generates a topic when available, and passes it toinsertRoom. The fallback tonullon 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
generateChatTitlefollows 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
generateChatTitleis called with the providedfirstMessageand the resulting title is passed toinsertRoom.
212-238: LGTM!The test correctly verifies that
generateChatTitleis not invoked whenfirstMessageis absent and thattopicdefaults tonull.
240-271: LGTM!Good coverage of the error handling path—verifies that when
generateChatTitlethrows, the handler gracefully falls back tonulltopic 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+ globaltry/catch)- Returns appropriate status codes with CORS headers on errors
1-6: No action required. The import style forgenerateUUIDis correct.Line 6 uses a default import, which is valid because
lib/uuid/generateUUID.tsexports 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:
setupChatRequestis called with correct parameterscreateUIMessageStreamandcreateUIMessageStreamResponseare invoked- The mock response is returned correctly
Consider adding a test for when
agent.stream()throws an error (inside the execute callback) to verify theonErrorhandler 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
handleChatStreamfor 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
clearAllMocksinbeforeEachandrestoreAllMocksinafterEach.
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
setupChatRequestorgenerateTextthrows, 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.
| response: { | ||
| messages: result.response.messages, | ||
| headers: result.response.headers, | ||
| body: result.response.body, | ||
| }, |
There was a problem hiding this comment.
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>
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>
There was a problem hiding this comment.
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 || !outputTokensevaluates0as falsy, causing the function to return0instead of calculating the actual cost when either token count is legitimately0. 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
nullon 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\nseparator 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 ofas anycasts.The repeated
as anycasts foraccountWithDetailsbypass TypeScript's type checking, which could hide issues if theAccountWithDetailsinterface changes in the future. Consider importing the type and usingPartial<>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
stepCountIsfunction 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
stopWhenis 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
0when both tokens are0, and it currently passes. However, it passes because the implementation incorrectly treats0as falsy in the!inputTokens || !outputTokenscheck, not because0 * price = 0. Once the truthiness bug ingetCreditUsage.tsis fixed to use== nullchecks, this test will correctly return0from 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:
- Updating the return type to
Promise<Memory>and removing the "or null" from JSDoc, or- 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: Unusedmessagesfield in ErrorContext.The
messagesfield is declared in the interface but never used informatErrorMessage. If this is intended for future use, consider removing it until needed to avoid confusion.Additionally, be aware that the
♻️ 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
sendErrorNotificationcall is not awaited. While this is intentional for non-blocking behavior (and the function has internal error handling), consider addingvoidoperator 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 emptyresponseMessagesarray.Given the potential crash when
responseMessagesis empty (flagged inhandleChatCompletion.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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (24)
lib/agents/generalAgent/__tests__/getGeneralAgent.test.tslib/ai/__tests__/getAvailableModels.test.tslib/ai/__tests__/getModel.test.tslib/ai/getAvailableModels.tslib/ai/getModel.tslib/ai/isEmbedModel.tslib/chat/__tests__/buildSystemPromptWithImages.test.tslib/chat/__tests__/handleChatCompletion.test.tslib/chat/handleChatCompletion.tslib/chat/validateMessages.tslib/credits/__tests__/getCreditUsage.test.tslib/credits/__tests__/handleChatCredits.test.tslib/credits/getCreditUsage.tslib/credits/handleChatCredits.tslib/emails/extractSendEmailResults.tslib/emails/handleSendEmailToolOutputs.tslib/errors/serializeError.tslib/files/__tests__/getKnowledgeBaseText.test.tslib/messages/__tests__/extractImageUrlsFromMessages.test.tslib/prompts/__tests__/getSystemPrompt.test.tslib/supabase/memories/upsertMemory.tslib/supabase/rooms/selectRoom.tslib/telegram/sendErrorNotification.tspackage.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.tslib/ai/isEmbedModel.tslib/credits/__tests__/getCreditUsage.test.tslib/credits/handleChatCredits.tslib/supabase/memories/upsertMemory.tslib/credits/__tests__/handleChatCredits.test.tslib/chat/handleChatCompletion.tslib/ai/getModel.tslib/credits/getCreditUsage.tslib/telegram/sendErrorNotification.tslib/agents/generalAgent/__tests__/getGeneralAgent.test.tslib/emails/handleSendEmailToolOutputs.tslib/emails/extractSendEmailResults.tslib/messages/__tests__/extractImageUrlsFromMessages.test.tslib/ai/__tests__/getAvailableModels.test.tslib/ai/__tests__/getModel.test.tslib/errors/serializeError.tslib/chat/__tests__/buildSystemPromptWithImages.test.tslib/supabase/rooms/selectRoom.tslib/ai/getAvailableModels.tslib/chat/validateMessages.tslib/chat/__tests__/handleChatCompletion.test.tslib/prompts/__tests__/getSystemPrompt.test.ts
!(lib/supabase)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
NEVER import
@/lib/supabase/serverClientoutside oflib/supabase/directory
Files:
lib/files/__tests__/getKnowledgeBaseText.test.tslib/ai/isEmbedModel.tslib/credits/__tests__/getCreditUsage.test.tslib/credits/handleChatCredits.tslib/supabase/memories/upsertMemory.tslib/credits/__tests__/handleChatCredits.test.tslib/chat/handleChatCompletion.tslib/ai/getModel.tslib/credits/getCreditUsage.tslib/telegram/sendErrorNotification.tslib/agents/generalAgent/__tests__/getGeneralAgent.test.tslib/emails/handleSendEmailToolOutputs.tslib/emails/extractSendEmailResults.tslib/messages/__tests__/extractImageUrlsFromMessages.test.tslib/ai/__tests__/getAvailableModels.test.tslib/ai/__tests__/getModel.test.tslib/errors/serializeError.tslib/chat/__tests__/buildSystemPromptWithImages.test.tslib/supabase/rooms/selectRoom.tspackage.jsonlib/ai/getAvailableModels.tslib/chat/validateMessages.tslib/chat/__tests__/handleChatCompletion.test.tslib/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.tslib/ai/getModel.tslib/emails/handleSendEmailToolOutputs.tslib/emails/extractSendEmailResults.tslib/ai/__tests__/getAvailableModels.test.tslib/ai/__tests__/getModel.test.tslib/ai/getAvailableModels.ts
lib/supabase/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
lib/supabase/**/*.{ts,tsx}: All Supabase database calls must be inlib/supabase/[table_name]/[function].ts
Supabase select functions should be namedselect[TableName].ts
Supabase insert functions should be namedinsert[TableName].ts
Supabase update functions should be namedupdate[TableName].ts
Supabase delete functions should be nameddelete[TableName].ts
Complex Supabase queries with joins should be namedget[Descriptive].ts
Files:
lib/supabase/memories/upsertMemory.tslib/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.tslib/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 usingTables<"rooms">correctly leverages the generated database types.Based on learnings, the naming convention
select[TableName].tsis 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.fetchand usesvi.clearAllMocks()inafterEachto 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 andres.okcheck.
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
indexOfcomparisons 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
extractImageUrlsFromMessagesfunction 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
buildSystemPromptWithImagesfunction, 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 forgetGeneralAgent.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
beforeEachis appropriate, andvi.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 === 0to detect embed models is reasonable, and the fallback behavior (returningfalsewhen 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
modelIdcontext 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.mockbefore the import, and thebeforeEach/afterEachhooks 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.errorinbeforeEachprevents 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
accountIdbefore 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
Errorinstances 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?.lengthas 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 viaPromise.allare 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.allfor 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 importingserverClientdirectly, 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
beforeEachfor resetting mocks and setting default implementations, andafterEachfor cleanup withrestoreAllMocks.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| function formatErrorMessage(context: ErrorContext): string { | ||
| const { path, error, roomId, email } = context; | ||
| const lines = [ | ||
| `*Error in ${path}*`, | ||
| "", | ||
| `*Error:* ${error.name}`, | ||
| `*Message:* ${error.message}`, | ||
| ]; |
There was a problem hiding this comment.
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>
Summary
ChatConfiginterface tolib/chat/types.tsthat extendsRoutingDecisionlib/chat/__tests__/types.test.tsTest plan
pnpm test- all 108 tests pass🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Utilities
Tests
✏️ Tip: You can customize this high-level summary in your review settings.