Skip to content

Conversation

@rSnapkoOpenOps
Copy link
Collaborator

@rSnapkoOpenOps rSnapkoOpenOps commented Dec 3, 2025

Fixes OPS-3173.

Deployed on UX

The idea is to let LLM decided whether it has enough context or no. In case AI decides that name is generated we set it as chat name. In case there is no enough data, we wait for the next LLM response and try to generate the name once again

Summary by CodeRabbit

  • New Features

    • Chat name generation now includes an isGenerated flag to indicate whether a name was successfully generated or a default was used.
  • Bug Fixes

    • Improved chat name generation reliability with enhanced validation and structured output handling.
  • Chores

    • Updated chat name API response format from chatName to include name and isGenerated fields for better clarity on generation status.

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

@linear
Copy link

linear bot commented Dec 3, 2025

@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

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

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

Note

Other AI code review bot(s) detected

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

Walkthrough

The PR refactors chat name generation to return a structured object { name, isGenerated } instead of a plain string. The AI prompt is updated for analytical guidance, a schema-based generation approach replaces text generation, and a message sanitization utility is introduced. Frontend and backend are updated to handle the new response shape.

Changes

Cohort / File(s) Summary
AI Prompt Refinement
ai-prompts/chat-name.txt
Updated prompt instruction from a task-directive to an analytical approach; replaced five-word constraint with schema-based rules (name length, isGenerated flag); added explicit output formatting notes and null/invalid handling guidance.
Shared Type Definition
packages/shared/src/lib/ai/chat/index.ts
Added new exported type GeneratedChatName with fields name (string or null) and isGenerated (boolean).
Backend Service & Utilities
packages/server/api/src/app/ai/chat/ai-chat.service.ts, packages/server/api/src/app/ai/chat/utils.ts
Service method generateChatName() now returns Promise<GeneratedChatName> instead of Promise<string>. Switched from generateText() to generateObject() with schema validation. Added new utility function sanitizeMessagesForChatName() that filters messages to user/assistant roles and extracts text content for cleaner LLM input.
Backend Controller
packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts
Chat-name endpoint response shape changed from { chatName: string } to { name: string, isGenerated: boolean }. Updated flow to call generateChatName(), conditionally update chat name based on isGenerated flag, and return the generated object.
Frontend API Client
packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts
Import added for GeneratedChatName. API call generateName() now uses generic type GeneratedChatName instead of { chatName: string }, matching the new backend response shape.
Frontend Hook
packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts
Hook useAssistantChat updated to store the generateName() return value and conditionally reset the attempted-name retry flag based on the isGenerated field.
Test Updates
packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts, packages/server/api/test/unit/ai/utils.test.ts
Test mocks and assertions updated to reflect the new GeneratedChatName return type. Added test coverage for sanitizeMessagesForChatName() utility including edge cases (tool/system message filtering, content trimming, text merging, non-text part filtering).

Sequence Diagram

sequenceDiagram
    participant Client as Frontend Client
    participant API as API Endpoint
    participant Controller as MCP Chat Controller
    participant Service as AI Chat Service
    participant LLM as Language Model
    participant DB as Database

    Client->>API: POST /chat-name { chatId }
    API->>Controller: route to generateChatName()
    Controller->>Controller: fetch chatHistory & projectId
    
    rect rgb(200, 220, 240)
    Note over Service,LLM: Schema-based Generation
    Controller->>Service: generateChatName(chatHistory, projectId)
    Service->>Service: sanitizeMessagesForChatName(history)
    Service->>LLM: generateObject(messages, schema)
    LLM-->>Service: { name: string, isGenerated: boolean }
    end
    
    Service-->>Controller: GeneratedChatName object
    
    alt isGenerated === true
        Controller->>DB: updateChatName(chatId, userId, name)
        DB-->>Controller: ✓ updated
    else isGenerated === false
        Note over Controller: Skip DB update, use default
    end
    
    Controller-->>API: { name: string, isGenerated: boolean }
    API-->>Client: { name, isGenerated }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • Key areas requiring attention:
    • Backend service method signature change (Promise<string>Promise<GeneratedChatName>) and schema-driven generation logic in ai-chat.service.ts
    • Message sanitization function logic in utils.ts to ensure correct filtering of roles and content extraction
    • Controller conditional logic based on isGenerated flag in ai-mcp-chat.controller.ts
    • Frontend hook state management of the isGenerated field in assistant-ui-chat-hook.ts
    • Comprehensive test coverage updates across multiple test files to validate the new contract

Poem

🐰 A chat name born from schema so bright,
No plain strings here—just objects done right!
With "isGenerated" flags flying high,
We sanitize messages as they pass by.
The frontend and backend now dance in sync,
Names that sparkle—quick as a wink! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ⚠️ Warning The PR description is incomplete and lacks required template sections. Key sections like 'Additional Notes' and 'Testing Checklist' are present but largely empty, and no rationale for the changes is provided. Complete the 'Additional Notes' section with context and reasoning. Check all applicable testing items in the 'Testing Checklist' to document verification performed. Consider adding a link to issue OPS-3173 for reference.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix chat name generation' clearly reflects the main change: refactoring the chat name generation logic to use LLM-driven decisions on name sufficiency.

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

@rSnapkoOpenOps
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

'role' in msg &&
msg.role === 'user',
);
const generated = await generateChatName(chatHistory, projectId);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The filtering is removed, having only user messages for generation is not enough

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

🧹 Nitpick comments (4)
packages/server/api/src/app/ai/chat/utils.ts (1)

67-95: Consider using || null for empty-string handling clarity.

The nullish coalescing operator (??) on lines 70 and 91 only converts null/undefined to null, not empty strings. The code works correctly because line 101 handles empty strings with a truthiness check, but using || null in extractText would be more explicit about the intent to treat empty strings as "no content."

   const extractText = (content: ModelMessage['content']): string | null => {
     if (typeof content === 'string') {
       const text = content.trim();
-      return text ?? null;
+      return text || null;
     }

     if (Array.isArray(content)) {
       const merged = (content as Array<unknown>)
         .reduce<string[]>((acc, part) => {
           if (
             part &&
             typeof part === 'object' &&
             'type' in (part as Record<string, unknown>)
           ) {
             const p = part as { type?: string; text?: string };
             if (p.type === 'text' && typeof p.text === 'string') {
               acc.push(p.text);
             }
           }
           return acc;
         }, [])
         .join('\n')
         .trim();

-      return merged ?? null;
+      return merged || null;
     }

     return null;
   };
packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts (3)

525-540: Mock should include name property for consistency.

The mock at line 525-527 returns only { isGenerated: false } without a name property. While the test expects the controller to provide the fallback "New Chat" name, the mock should ideally match the actual service contract for consistency.

Consider updating the mock to match the expected service response shape:

-      (generateChatName as jest.Mock).mockResolvedValue({
-        isGenerated: false,
-      });
+      (generateChatName as jest.Mock).mockResolvedValue({
+        isGenerated: false,
+        name: undefined,
+      });

This makes it clearer that the service may return undefined for the name when isGenerated is false, and the controller handles the fallback.


542-558: Test description doesn't match implementation.

The test description mentions "if LLM returns empty string", but the mock at line 546 returns { isGenerated: false } without any name property. The test would be more accurate if either:

  1. The description is updated to match the mock behavior, or
  2. The mock explicitly returns an empty string: { isGenerated: false, name: '' }

Update the mock to match the test description:

-      (generateChatName as jest.Mock).mockResolvedValue({ isGenerated: false });
+      (generateChatName as jest.Mock).mockResolvedValue({ 
+        isGenerated: false, 
+        name: '' 
+      });

Or update the test description to: "should return 'New Chat' when isGenerated is false"


353-622: Consider adding test coverage for edge cases.

The test suite covers the main scenarios well, but consider adding tests for the following edge cases to ensure robustness:

  1. Very long generated names: Verify truncation behavior if the LLM generates an excessively long name (e.g., >100 characters).
  2. Generated names with special characters: Verify sanitization of potentially problematic characters (e.g., newlines, tabs, control characters).
  3. Inconsistent service response: Verify behavior when isGenerated is true but name is empty, undefined, or whitespace-only.

Example test case to add:

it('should handle excessively long generated names', async () => {
  const longName = 'A'.repeat(200);
  (getConversation as jest.Mock).mockResolvedValue({
    chatHistory: [{ role: 'user', content: 'Test' }],
  });
  (generateChatName as jest.Mock).mockResolvedValue({
    isGenerated: true,
    name: longName,
  });

  await postHandler(
    { ...mockRequest, body: { chatId: 'test-chat-id' } } as FastifyRequest,
    mockReply as unknown as FastifyReply,
  );

  // Verify truncation or rejection
  expect(mockReply.code).toHaveBeenCalledWith(200);
  expect(mockReply.send).toHaveBeenCalledWith({
    isGenerated: true,
    name: expect.stringMatching(/^.{1,100}$/), // Expect max 100 chars
  });
});
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d79539f and 255c757.

📒 Files selected for processing (9)
  • ai-prompts/chat-name.txt (1 hunks)
  • packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts (2 hunks)
  • packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts (1 hunks)
  • packages/server/api/src/app/ai/chat/ai-chat.service.ts (3 hunks)
  • packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts (2 hunks)
  • packages/server/api/src/app/ai/chat/utils.ts (1 hunks)
  • packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts (11 hunks)
  • packages/server/api/test/unit/ai/utils.test.ts (2 hunks)
  • packages/shared/src/lib/ai/chat/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Indentation: 2 spaces for TypeScript/JavaScript
Braces required for all control blocks, even single-line
One space between keywords and parentheses: if (condition) {
Use camelCase for variables and functions
Use PascalCase for classes and types
Use UPPER_SNAKE_CASE for constants
Use lowercase with hyphens for file names (e.g., user-profile.ts)
Prefer const over let or var in TypeScript/JavaScript
Prefer arrow functions for callbacks and functional components in TypeScript/JavaScript

Files:

  • packages/shared/src/lib/ai/chat/index.ts
  • packages/server/api/test/unit/ai/utils.test.ts
  • packages/server/api/src/app/ai/chat/utils.ts
  • packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts
  • packages/server/api/src/app/ai/chat/ai-chat.service.ts
  • packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts
  • packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts
  • packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{ts,tsx}: Use types and interfaces where appropriate in TypeScript
Use explicit return types for exported functions in TypeScript

Files:

  • packages/shared/src/lib/ai/chat/index.ts
  • packages/server/api/test/unit/ai/utils.test.ts
  • packages/server/api/src/app/ai/chat/utils.ts
  • packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts
  • packages/server/api/src/app/ai/chat/ai-chat.service.ts
  • packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts
  • packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts
  • packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{tsx,ts}: Frontend tech stack must strictly use: React 18, Zustand, react-query v5, shadcn, and Axios with qs package for query strings
Follow best practices for React hooks
Prefer small, composable components and extract helper functions where possible
Use cn utility to group tailwind classnames in React components

Files:

  • packages/shared/src/lib/ai/chat/index.ts
  • packages/server/api/test/unit/ai/utils.test.ts
  • packages/server/api/src/app/ai/chat/utils.ts
  • packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts
  • packages/server/api/src/app/ai/chat/ai-chat.service.ts
  • packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts
  • packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts
  • packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts
🧬 Code graph analysis (5)
packages/server/api/test/unit/ai/utils.test.ts (1)
packages/server/api/src/app/ai/chat/utils.ts (1)
  • sanitizeMessagesForChatName (61-104)
packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts (1)
packages/server/api/src/app/ai/chat/ai-chat.service.ts (2)
  • generateChatName (102-126)
  • updateChatName (128-142)
packages/server/api/src/app/ai/chat/ai-chat.service.ts (3)
packages/shared/src/lib/ai/chat/index.ts (2)
  • GeneratedChatName (133-136)
  • GeneratedChatName (137-137)
packages/server/api/src/app/ai/chat/utils.ts (1)
  • sanitizeMessagesForChatName (61-104)
packages/openops/src/lib/ai/providers.ts (1)
  • isLLMTelemetryEnabled (136-138)
packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts (2)
packages/react-ui/src/app/lib/api.ts (1)
  • api (33-97)
packages/shared/src/lib/ai/chat/index.ts (2)
  • GeneratedChatName (133-136)
  • GeneratedChatName (137-137)
packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts (1)
packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts (1)
  • aiAssistantChatHistoryApi (4-24)
🔇 Additional comments (17)
packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts (1)

246-254: LGTM! Clean handling of the new structured response.

The conditional retry logic is well-implemented: clearing hasAttemptedNameGenerationRef when isGenerated is false allows the system to retry name generation on subsequent messages when more context becomes available.

ai-prompts/chat-name.txt (1)

1-13: Well-structured prompt with clear output contract.

The prompt clearly defines the expected behavior and aligns with the GeneratedChatName schema. The distinction between confident generation (isGenerated: true) and insufficient context (isGenerated: false) enables graceful fallback handling.

packages/shared/src/lib/ai/chat/index.ts (1)

132-137: LGTM! Type definition correctly models the response contract.

The GeneratedChatName type properly represents both successful generation (isGenerated: true, name: string) and fallback cases (isGenerated: false, name: null).

packages/server/api/src/app/ai/chat/utils.ts (1)

55-104: Well-designed sanitization utility.

The function cleanly separates concerns: role filtering, text extraction, and empty content elimination. The implementation correctly handles both string content and structured array content with text parts.

packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts (1)

2-2: LGTM! API type updated to match new response shape.

The import and return type correctly reflect the server-side changes, ensuring type safety across the client-server boundary.

Also applies to: 11-14

packages/server/api/test/unit/ai/utils.test.ts (1)

808-885: Comprehensive test coverage for sanitizeMessagesForChatName.

The tests effectively cover the key behaviors: role filtering (dropping system/tool), whitespace trimming, text part merging with newlines, non-text part filtering, and empty result handling.

packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts (2)

257-265: Defensive null check on generated.name is appropriate.

The condition generated.isGenerated && generated.name guards against edge cases where the LLM might return isGenerated: true with a null/empty name, even though the prompt instructs otherwise.


241-271: Confirm all consumers of /chat-name endpoint have been updated for the response shape change.

The endpoint response changed from { chatName } to { name, isGenerated }. This is a breaking change, but verification confirms all identified consumers have been updated: ai-assistant-chat-history-api.ts (line 12) already types the response as GeneratedChatName, and assistant-ui-chat-hook.ts (line 247) already accesses result.isGenerated. No orphaned consumers using the old response shape were found. Ensure this change is documented in release notes if this is a versioned API.

packages/server/api/src/app/ai/chat/ai-chat.service.ts (2)

93-100: Schema consistency: Zod schema matches TypeBox definition.

The Zod schema generatedChatNameSchema mirrors the TypeBox GeneratedChatName type in the shared package. The .max(100) constraint aligns with MAX_CHAT_NAME_LENGTH in the controller.


102-126: Well-structured refactor to object-based generation.

The switch from generateText to generateObject with a strict schema ensures type-safe, structured responses. Message sanitization before LLM invocation reduces noise and improves generation quality.

packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts (7)

409-414: LGTM!

The test correctly verifies that when chatHistory is empty, the controller returns { name: 'New Chat', isGenerated: false } without calling generateChatName or updateChatName. This is the expected short-circuit behavior.


418-446: Test logic appears inconsistent with description.

The test description states "should return 'New Chat' when chatHistory has no user messages", but the test still calls generateChatName with the chatHistory (line 441-444). This suggests that:

  1. The controller now always calls generateChatName when chatHistory is non-empty (regardless of user message presence)
  2. The service is responsible for returning { isGenerated: false } when no valid content exists

However, the mock at line 429 returns { isGenerated: false } without a name property, which could mask bugs if the service doesn't consistently return this shape.

Consider adding an assertion to verify the service received the expected chatHistory:

 expect(generateChatName).toHaveBeenCalledWith(
   chatHistory,
   'test-project-id',
 );
+// Verify chatHistory contains no user messages
+expect(chatHistory.filter(m => m.role === 'user')).toHaveLength(0);

Also verify that the actual service implementation handles missing name property correctly when isGenerated is false.


448-483: LGTM!

The test correctly verifies that the controller passes the raw chatHistory without filtering invalid or malformed messages. The responsibility for validation and filtering has been moved to the service layer, which is a good separation of concerns.


485-516: LGTM!

The test correctly verifies that generateChatName receives the complete chatHistory including all user and assistant messages, and that updateChatName is called with the generated name.


560-588: LGTM!

The test correctly verifies that when isGenerated is true with a valid name, the controller calls updateChatName to persist it and returns the expected response format.


590-621: LGTM!

The error handling tests correctly verify that both getConversation and generateChatName errors are caught and return appropriate 500 error responses.


370-373: The service implementation correctly handles full chatHistory through internal sanitization.

The generateChatName function receives the complete message history but immediately sanitizes it via sanitizeMessagesForChatName, which filters messages to keep only user and assistant roles while removing system and tool messages. This internal filtering is appropriate and handles all message types correctly. The test expectations align with the actual implementation behavior.

@rSnapkoOpenOps rSnapkoOpenOps marked this pull request as ready for review December 3, 2025 15:39
Copilot AI review requested due to automatic review settings December 3, 2025 15:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves chat name generation by allowing the LLM to indicate when it has insufficient context to generate a meaningful name. Instead of always generating a name (which could be generic), the system now returns a structured response with an isGenerated flag. When false, the system returns "New Chat" and can retry name generation on subsequent LLM responses.

Key Changes:

  • Modified chat name generation to return a structured object containing both the name and an isGenerated boolean flag
  • Introduced message sanitization to extract only user/assistant text content for name generation
  • Updated retry logic to attempt name generation again when isGenerated is false

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/shared/src/lib/ai/chat/index.ts Added GeneratedChatName type definition with name and isGenerated fields
packages/server/api/src/app/ai/chat/ai-chat.service.ts Updated generateChatName to use structured output with schema validation and message sanitization
packages/server/api/src/app/ai/chat/utils.ts Added sanitizeMessagesForChatName function to filter and clean messages for name generation
packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts Updated controller to handle structured chat name response and conditionally update chat names
packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts Modified to reset name generation attempt flag when isGenerated is false
packages/react-ui/src/app/features/ai/lib/ai-assistant-chat-history-api.ts Updated API return type to GeneratedChatName
ai-prompts/chat-name.txt Enhanced prompt to instruct LLM to set isGenerated flag based on context sufficiency
packages/server/api/test/unit/ai/utils.test.ts Added comprehensive tests for sanitizeMessagesForChatName function
packages/server/api/test/unit/ai/ai-mcp-chat.controller.test.ts Updated tests to match new structured response format

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const sanitizedMessages: ModelMessage[] =
sanitizeMessagesForChatName(messages);

if (isEmpty(sanitizedMessages.length)) {
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The isEmpty function is being called with sanitizedMessages.length (a number), but it likely expects an array or object. This should be if (sanitizedMessages.length === 0) or if (isEmpty(sanitizedMessages)).

Suggested change
if (isEmpty(sanitizedMessages.length)) {
if (sanitizedMessages.length === 0) {

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link

greptile-apps bot commented Dec 3, 2025

Greptile Overview

Greptile Summary

This PR improves chat name generation by letting the LLM decide if it has sufficient context to generate a meaningful name. Instead of always generating a name (which might be generic), the LLM now returns a structured response with isGenerated: boolean and name: string | null. If the LLM indicates insufficient context, the frontend will retry name generation on subsequent messages.

Key changes:

  • Changed API response from { chatName: string } to { name: string | null, isGenerated: boolean }
  • Added sanitizeMessagesForChatName utility to filter messages to only user/assistant text content
  • Updated prompt to instruct LLM to set isGenerated: false when context is insufficient
  • Frontend now resets the name generation flag when isGenerated: false, allowing retry

Critical issue found:

  • Bug in ai-chat.service.ts:116 where isEmpty(sanitizedMessages.length) passes a number to isEmpty(), which always returns false for numbers. This breaks the early return logic for empty sanitized messages.

Confidence Score: 2/5

  • Contains a logic bug that prevents the empty message early return from ever executing.
  • The PR contains a clear bug where isEmpty(sanitizedMessages.length) is used instead of isEmpty(sanitizedMessages). The isEmpty function returns false for numbers, so the early return for empty sanitized messages will never trigger. This could cause unnecessary LLM calls with empty message arrays.
  • packages/server/api/src/app/ai/chat/ai-chat.service.ts - line 116 has a bug with isEmpty() usage

Important Files Changed

File Analysis

Filename Score Overview
packages/server/api/src/app/ai/chat/ai-chat.service.ts 2/5 Contains a bug where isEmpty(sanitizedMessages.length) always returns false for numbers, breaking the early return logic.
packages/react-ui/src/app/features/ai/lib/assistant-ui-chat-hook.ts 5/5 Updated to handle isGenerated flag, allowing retry of name generation when LLM indicates insufficient context.
packages/server/api/src/app/ai/chat/ai-mcp-chat.controller.ts 5/5 Controller now passes full chat history to generateChatName and handles isGenerated flag in response.
packages/server/api/src/app/ai/chat/utils.ts 5/5 Added sanitizeMessagesForChatName function to filter and clean messages for chat name generation.
packages/shared/src/lib/ai/chat/index.ts 5/5 Added GeneratedChatName type with name and isGenerated fields for the new API contract.

Sequence Diagram

sequenceDiagram
    participant UI as Frontend
    participant API as Chat Controller
    participant Service as AI Chat Service
    participant LLM as Language Model

    UI->>API: POST /chat-name { chatId }
    API->>Service: getConversation(chatId)
    Service-->>API: chatHistory[]
    
    alt chatHistory empty
        API-->>UI: { name: "New Chat", isGenerated: false }
    else chatHistory has messages
        API->>Service: generateChatName(chatHistory, projectId)
        Service->>Service: sanitizeMessagesForChatName(messages)
        Service->>LLM: generateObject(sanitizedMessages, schema)
        LLM-->>Service: { name, isGenerated }
        Service-->>API: GeneratedChatName
        
        alt isGenerated && name
            API->>Service: updateChatName(chatId, name)
        end
        
        API-->>UI: { name, isGenerated }
    end
    
    UI->>UI: if !isGenerated, reset flag to retry later
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
return { name: null, isGenerated: false };
}

const result = await generateObject({
Copy link
Contributor

@alexandrudanpop alexandrudanpop Dec 4, 2025

Choose a reason for hiding this comment

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

As LLMs don't always generate correct structured output, you can make it a bit more reliable using the approach below

experimental_repairText: async ({ text }) => repairText(text),

return { name: null, isGenerated: false };
}

const result = await generateObject({
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, you could wrap in try catch and return {isGenerated: false} in case there is an error and log the error

.max(100)
.nullable()
.describe('Conversation name or null if it was not generated'),
isGenerated: z.boolean().describe('Whether the name was generated or not'),
Copy link
Contributor

Choose a reason for hiding this comment

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

@rSnapkoOpenOps why do you need isGenerated?
you can deduce that from checking if (name). The less logic the LLM needs to do, the better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I've tried that, but ai still adds dummy names

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 4, 2025

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.

5 participants