Skip to content

Conversation

@rossmanko
Copy link
Contributor

@rossmanko rossmanko commented Oct 21, 2025

Summary by CodeRabbit

  • New Features

    • Automatic conversation summarization with started/completed UI indicators; summaries are persisted and prepended into chat history.
  • Changes

    • Token limits now apply per subscription; streaming and tool token budgets reduced.
    • Message editor enforces token limits and shows an error toast when exceeded.
    • Tool outputs no longer expose file path lists; Python tool no longer shows file-count text.
  • Bug Fixes

    • Chat deletion and message regeneration clean up summaries in a best-effort, non-blocking way.
    • Regenerate button disabled while a regeneration is in progress; summaries invalidated when relevant messages are removed.

@vercel
Copy link

vercel bot commented Oct 21, 2025

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

Project Deployment Preview Comments Updated (UTC)
hackerai Ready Ready Preview Comment Oct 22, 2025 4:20pm

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds end-to-end conversation summarization: schema and Convex ops for summaries, backend summary lifecycle and invalidation, a summarization utility and chat-handler integration (events + persistence), UI parts and components for summarization, token-limit refactors, and assorted UI/tool adjustments.

Changes

Cohort / File(s) Summary
Token management
app/components/ChatInput.tsx, lib/token-utils.ts, lib/utils/file-token-utils.ts
Centralized max-token lookup via getMaxTokensForSubscription(subscription); renamed MAX_TOKENS_PROMAX_TOKENS_PRO_AND_TEAM; added MAX_TOKENS_FILE and FILE_READ_TRUNCATION_MESSAGE; lowered stream/tool token caps (4096→2048); updated file-token APIs (skipFileTokens, Id<"files">-keyed maps).
Summarization utility
lib/utils/message-summarization.ts
New checkAndSummarizeIfNeeded(...) that decides summarization by token thresholds, generates an AI summary, and returns { needsSummarization, summarizedMessages, cutoffMessageId, summaryText }.
Chat handler integration
lib/api/chat-handler.ts, lib/ai/providers.ts, lib/utils/stream-writer-utils.ts
Integrates summarization into prepareStep (emit data-summarization start/complete), persists summaries via backend mutation, switches to summarized messages for model input, registers a "summarization-model" provider, and adds stream writer helpers for summarization and upload status.
DB actions & retrieval
lib/db/actions.ts
Added saveChatSummary(...) and getLatestSummary(...); getMessagesByChatId now injects a <conversation_summary> UI message (UUID) when a latest summary exists; added mode handling for file-token trimming.
Backend schema & Convex ops
convex/schema.ts, convex/chats.ts, convex/messages.ts
Added chat_summaries table and chats.latest_summary_id; added saveLatestSummary mutation and getLatestSummaryForBackend query (service-key guarded); delete flows attempt summary cleanup; added checkAndInvalidateSummary to clear summaries when referenced messages are deleted.
Frontend UI components
app/components/MessagePartHandler.tsx, app/components/tools/SummarizationHandler.tsx
Added data-summarization part handling and a new SummarizationHandler component showing WandSparkles and a ShimmerText "started" state or a muted completed message.
Tooling / Python tool changes
lib/ai/tools/python.ts, app/components/tools/PythonToolHandler.tsx
Removed per-run files list from Python tool results and removed file-count display in PythonToolHandler UI (now shows static "Executed Python").
Message regeneration & UI actions
convex/messages.ts, app/components/MessageActions.tsx
Added internal summary invalidation after regenerate; introduced isRegenerating guard and handleRegenerate to prevent concurrent regenerations and disable regenerate button while active.
Miscellaneous
lib/utils/terminal-executor.ts
Minor comment removal; no behavioral change.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Handler as Chat Handler
    participant Summarizer as MessageSummarizer
    participant Model as Language Model
    participant Convex as Convex Backend
    participant DB as DB Actions
    participant UI as UI Stream

    Client->>Handler: prepareStep (non-temp)
    activate Handler
    Handler->>Summarizer: checkAndSummarizeIfNeeded(messages, subscription, model)
    activate Summarizer
    Summarizer->>Summarizer: compute tokens, decide
    alt needsSummarization
        Summarizer->>Model: generate summary
        Model-->>Summarizer: summaryText
        Summarizer-->>Handler: summarizedMessages, cutoffId, summaryText
        Handler->>UI: emit data-summarization (started)
        Handler->>Convex: saveLatestSummary(chatId, summaryText, cutoffId)
        Convex->>DB: persist chat_summaries & update chats.latest_summary_id
        Handler->>UI: emit data-summarization (completed)
        Handler->>Handler: use summarizedMessages for model input
    else noSummarization
        Summarizer-->>Handler: needsSummarization = false
    end
    Handler->>Convex: save assistant message(s) (+ summarization parts)
    Convex-->>DB: persist messages
    Handler-->>UI: stream assistant response
    deactivate Handler
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Daily branch 2025 10 05 #46 — touches lib/db/actions.ts and getMessagesByChatId logic; likely overlaps on summary injection and message truncation changes.
  • Daily branch 2025 09 21 #35 — modifies lib/ai/providers.ts; likely overlaps with adding/registering the new summarization provider.
  • feat: todo-write tool #11 — modifies app/components/MessagePartHandler.tsx; likely overlaps with adding new part-type handling and tool handler imports.

Poem

🐇 I hopped through tokens, trimmed the thread,
Wove short summaries where long words spread.
Wand sparks shimmer as the recap begins,
A tiny tail for the chat that thins.
— a rabbit, nibbling tidy wins ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: add automatic conversation summarization for agent mode" accurately reflects the primary objective of the changeset. The PR introduces a comprehensive feature that implements automatic conversation summarization specifically for agent mode, as evidenced by the core changes in lib/api/chat-handler.ts where summarization logic is triggered during prepareStep for agent mode. Supporting infrastructure includes new backend mutations and queries for persisting summaries (convex/chats.ts), a dedicated summarization utility module (lib/utils/message-summarization.ts), new UI components for displaying summarization status (SummarizationHandler.tsx), and related token management improvements. The title is specific, clear, and avoids vague terms or file listings.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/agent-conversation-summarization

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

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: 3

🧹 Nitpick comments (5)
app/components/tools/SummarizationHandler.tsx (2)

5-9: Consider adding a more specific type for the part prop.

The part prop is currently typed as any, which reduces type safety and could lead to runtime errors if the expected structure isn't present.

Consider defining a specific type for summarization parts:

+interface SummarizationPart {
+  data: {
+    status: "started" | "completed";
+    message: string;
+  };
+  type: "data-summarization";
+}
+
 interface SummarizationHandlerProps {
   message: UIMessage;
-  part: any;
+  part: SummarizationPart;
   partIndex: number;
 }

22-28: Add optional chaining for safer property access.

Accessing part.data.status and part.data.message without validation could cause runtime errors if the structure is unexpected.

Apply this diff to add defensive checks:

-      {part.data.status === "started" ? (
-        <ShimmerText className="text-sm">{part.data.message}...</ShimmerText>
+      {part.data?.status === "started" ? (
+        <ShimmerText className="text-sm">{part.data?.message ?? "Summarizing"}...</ShimmerText>
       ) : (
         <span className="text-sm text-muted-foreground">
-          {part.data.message}
+          {part.data?.message ?? "Summary completed"}
         </span>
       )}
convex/messages.ts (1)

19-60: Add proper type for the ctx parameter.

The ctx parameter is typed as any, reducing type safety. Since this is used within mutation handlers, it should be typed as MutationCtx or the appropriate Convex context type.

+import type { MutationCtx } from "./_generated/server";
+
-const checkAndInvalidateSummary = async (
-  ctx: any,
+const checkAndInvalidateSummary = async (
+  ctx: MutationCtx,
   chatId: string,
   deletedMessageIds: (string | undefined)[],
 ) => {
lib/db/actions.ts (1)

279-312: Consider mode verification before summary injection.

The comment indicates "agent mode only", but there's no runtime check to prevent summary injection when the current request is in "ask" mode. If a chat was previously used in agent mode (with a summary) and then accessed in ask mode, the summary would still be injected. Consider adding a mode parameter and guard condition if this behavior is unintended.

lib/utils/message-summarization.ts (1)

47-50: Remove unnecessary type cast.

Line 48 casts fileIds as any, but extractAllFileIdsFromMessages returns Array<Id<"files">> and getFileTokensByIds expects Id<"files">[], which are equivalent types. The cast is unnecessary and should be removed for type safety.

Apply this diff:

-  const tokens = await getFileTokensByIds(fileIds as any);
+  const tokens = await getFileTokensByIds(fileIds);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad9152d and bf8a4aa.

⛔ Files ignored due to path filters (4)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
  • convex/_generated/api.js is excluded by !**/_generated/**
  • convex/_generated/server.d.ts is excluded by !**/_generated/**
  • convex/_generated/server.js is excluded by !**/_generated/**
📒 Files selected for processing (13)
  • app/components/ChatInput.tsx (2 hunks)
  • app/components/MessagePartHandler.tsx (2 hunks)
  • app/components/tools/SummarizationHandler.tsx (1 hunks)
  • convex/chats.ts (5 hunks)
  • convex/messages.ts (2 hunks)
  • convex/schema.ts (2 hunks)
  • lib/ai/providers.ts (1 hunks)
  • lib/api/chat-handler.ts (6 hunks)
  • lib/db/actions.ts (4 hunks)
  • lib/token-utils.ts (2 hunks)
  • lib/utils/file-token-utils.ts (1 hunks)
  • lib/utils/message-summarization.ts (1 hunks)
  • lib/utils/terminal-executor.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • lib/utils/terminal-executor.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • app/components/tools/SummarizationHandler.tsx
  • convex/schema.ts
  • lib/utils/message-summarization.ts
  • lib/utils/file-token-utils.ts
  • convex/messages.ts
  • lib/token-utils.ts
  • lib/db/actions.ts
  • lib/ai/providers.ts
  • convex/chats.ts
  • app/components/ChatInput.tsx
  • app/components/MessagePartHandler.tsx
  • lib/api/chat-handler.ts
convex/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

convex/**/*.{ts,tsx,js,jsx}: ALWAYS use the new Convex function syntax (query/mutation/action object with args/returns/handler) when defining Convex functions
When a Convex function returns null, include returns: v.null() and return null
Use internalQuery/internalMutation/internalAction for private functions; do not expose sensitive logic with public query/mutation/action
Do NOT register functions via the api or internal objects
ALWAYS provide args and returns validators for query, mutation, action and their internal variants; use v.null() when no value is returned
From Convex functions, use ctx.runQuery/ctx.runMutation/ctx.runAction to call other functions (not direct function calls)
Pass a FunctionReference (from api/internal) to ctx.runQuery/ctx.runMutation/ctx.runAction; do NOT pass the callee function directly
Only call an action from another action when crossing runtimes (e.g., V8 to Node); otherwise refactor shared code into helpers
Minimize calls from actions to queries/mutations to reduce race conditions; prefer fewer transactional boundaries
Organize public query/mutation/action functions under convex/ to align with file-based routing
For pagination, validate args with paginationOptsValidator and call .paginate(args.paginationOpts) on queries
Use v.int64() instead of deprecated v.bigint() for signed 64-bit integers
Use v.record() for record-like structures; v.map() and v.set() are not supported
Do NOT use query().filter(...); define an index in schema and use withIndex instead
Convex queries do not support .delete(); collect or iterate results and call ctx.db.delete(_id) for each
Use .unique() to fetch exactly one document and fail on multiples
When async-iterating query results, prefer for await (const row of query) over .collect() or .take(n)
Use ctx.db.replace to fully replace an existing document; ctx.db.patch to shallow-merge updates
Files defining actions that use Node built-ins must start with "use node"
Never access ctx.db from within an action
D...

Files:

  • convex/schema.ts
  • convex/messages.ts
  • convex/chats.ts
convex/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

When run-calling a function in the same file, add an explicit return type annotation on the await result to avoid TS circularity issues

Files:

  • convex/schema.ts
  • convex/messages.ts
  • convex/chats.ts
convex/schema.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

convex/schema.{ts,tsx,js,jsx}: Define the database schema in convex/schema.ts
Import schema helpers (defineSchema, defineTable) from convex/server in the schema file
Include all index fields in the index name (e.g., by_field1_and_field2 for ["field1","field2"])

Files:

  • convex/schema.ts
🧬 Code graph analysis (7)
app/components/tools/SummarizationHandler.tsx (1)
app/components/ShimmerText.tsx (1)
  • ShimmerText (12-39)
lib/utils/message-summarization.ts (3)
types/chat.ts (1)
  • SubscriptionTier (7-7)
lib/utils/file-token-utils.ts (2)
  • extractAllFileIdsFromMessages (75-88)
  • getFileTokensByIds (43-68)
lib/token-utils.ts (2)
  • countMessagesTokens (92-101)
  • getMaxTokensForSubscription (13-20)
lib/db/actions.ts (1)
lib/errors.ts (1)
  • ChatSDKError (36-74)
convex/chats.ts (1)
convex/tempStreams.ts (1)
  • validateServiceKey (4-8)
app/components/ChatInput.tsx (1)
lib/token-utils.ts (1)
  • getMaxTokensForSubscription (13-20)
app/components/MessagePartHandler.tsx (1)
app/components/tools/SummarizationHandler.tsx (1)
  • SummarizationHandler (11-31)
lib/api/chat-handler.ts (3)
lib/utils/message-summarization.ts (1)
  • checkAndSummarizeIfNeeded (27-118)
lib/db/actions.ts (2)
  • saveChatSummary (606-634)
  • saveMessage (56-91)
convex/messages.ts (1)
  • saveMessage (93-201)
🔇 Additional comments (21)
lib/utils/file-token-utils.ts (1)

90-96: LGTM: Documentation updates align with centralized token handling.

The JSDoc updates accurately reflect the function's responsibilities and align with the new centralized token limit management approach introduced in this PR.

lib/ai/providers.ts (1)

16-16: LGTM: New summarization model provider added.

The new provider entry follows the established pattern and integrates seamlessly with the existing tracing infrastructure.

app/components/MessagePartHandler.tsx (1)

10-10: LGTM: Summarization handler integrated correctly.

The new case follows the established pattern for handling custom part types, and the props passed match the component's interface.

Also applies to: 61-68

lib/token-utils.ts (2)

6-6: LGTM: Constant renamed to reflect team tier support.

The rename from MAX_TOKENS_PRO to MAX_TOKENS_PRO_AND_TEAM accurately reflects that both subscription tiers receive the same token limit.

Also applies to: 17-19


23-24: Token limit reduction is intentional but lacks test coverage verification.

The 50% reduction from 4096 to 2048 tokens is confirmed as intentional, implemented in commit bf8a4aa as part of the automatic conversation summarization feature (Oct 21, 2025). This aligns with the summarization feature goals.

However, verification reveals no test files validate these token limits for existing use cases. Without test results confirming behavior with the new limits, the impact on tool output truncation (file reads, terminal output, Python execution) cannot be definitively assessed.

To address the review concerns:

  • Whether users will experience unexpected truncation with the new limits requires testing the affected code paths (Python tool execution, terminal output streaming, file read operations)
  • Recommend adding test coverage for token truncation edge cases before merging
convex/messages.ts (2)

28-31: LGTM: Proper type guard for filtering undefined values.

The type guard correctly narrows the type from (string | undefined)[] to string[].


984-989: LGTM: Summary invalidation integrated into regeneration flow.

The invalidation check is correctly placed after message deletion and passes the appropriate message IDs.

app/components/ChatInput.tsx (1)

37-37: LGTM: Token limit calculation centralized.

The change correctly uses the new getMaxTokensForSubscription helper, improving maintainability by consolidating token limit logic in a single location.

Also applies to: 183-183

convex/schema.ts (1)

31-31: LGTM: Schema changes support the summarization feature.

Verification confirms the assessment: summary_up_to_message_id is never used in query filters. Summaries are fetched by _id and the field is checked in memory (e.g., convex/messages.ts:46). The index on chat_id sufficiently enables efficient summary lookups for a given chat. No additional index on summary_up_to_message_id is necessary.

lib/api/chat-handler.ts (4)

7-7: LGTM! Imports and initialization are correct.

The new imports and summarizationParts initialization properly support the summarization feature.

Also applies to: 34-34, 46-46, 171-172


245-251: LGTM! The finalMessages pattern enables clean summarization.

The mutable reference allows prepareStep to substitute summarized messages without side effects on processedMessages.


456-473: LGTM! Summarization parts are correctly persisted.

The logic properly prepends summarization events to assistant messages for persistence, ensuring the summarization context is retained in the database.


254-314: No issues found. The "summarization-model" identifier is properly defined.

The identifier is correctly configured in lib/ai/providers.ts:16 as "summarization-model": xai("grok-4-fast-non-reasoning") and is part of the valid provider configuration. The xai provider is properly imported from @ai-sdk/xai. The usage in chat-handler.ts:266 correctly references this definition.

Likely an incorrect or invalid review comment.

lib/db/actions.ts (3)

216-219: LGTM! Summary fetching is properly guarded.

The conditional check ensures getLatestSummary is only called when a summary exists.


606-634: LGTM! Error handling is comprehensive.

The function properly logs errors with context and throws a structured ChatSDKError.


636-647: LGTM! Graceful error handling for summary retrieval.

Returning null on error is appropriate for this getter function and maintains consistency with other similar functions in this file.

lib/utils/message-summarization.ts (2)

61-76: LGTM! Message splitting logic is correct.

The slice operations properly partition messages into those to summarize and those to keep unsummarized, with the cutoff ID correctly identified.


78-117: LGTM! AI summary generation with proper error handling.

The fallback summary ensures the feature degrades gracefully if the AI generation fails, and the XML-wrapped user message correctly injects the summary context into the conversation.

convex/chats.ts (3)

46-46: LGTM! Return types properly extended.

The latest_summary_id field is correctly typed as optional with proper table reference.

Also applies to: 130-130


555-581: LGTM! Comprehensive cleanup with defensive programming.

The deletion logic handles both the referenced latest_summary_id and queries for any orphaned summaries, ensuring complete cleanup even if prior operations failed. The error handling allows deletion to proceed despite cleanup failures.


712-738: LGTM! Consistent cleanup logic.

The summary cleanup in deleteAllChats follows the same pattern as deleteChat, ensuring consistency.

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: 0

♻️ Duplicate comments (2)
convex/chats.ts (2)

756-811: Use internalMutation instead of mutation.

This mutation is backend-only (validates service key) and should use internalMutation to properly restrict access. This issue was already raised in a previous review comment but hasn't been addressed yet.

As per coding guidelines: "Use internalQuery/internalMutation/internalAction for private functions; do not expose sensitive logic with public query/mutation/action."

Based on learnings.


817-859: Use internalQuery instead of query.

This query is backend-only (validates service key) and should use internalQuery to properly restrict access. This issue was already raised in a previous review comment but hasn't been addressed yet.

As per coding guidelines: "Use internalQuery/internalMutation/internalAction for private functions; do not expose sensitive logic with public query/mutation/action."

Based on learnings.

🧹 Nitpick comments (2)
convex/chats.ts (2)

555-581: Consider optimizing summary deletion to avoid potential double deletion.

The code first deletes chat.latest_summary_id (lines 556-566), then queries and deletes all summaries by chat_id (lines 569-581). If the latest summary is still in the database when queried, it will be deleted twice—the second attempt will fail gracefully but generates an unnecessary operation and error log.

Consider one of these approaches:

Option 1: Query all summaries once and delete them without special-casing latest_summary_id:

-      // Delete chat summaries
-      if (chat.latest_summary_id) {
-        try {
-          await ctx.db.delete(chat.latest_summary_id);
-        } catch (error) {
-          console.error(
-            `Failed to delete summary ${chat.latest_summary_id}:`,
-            error,
-          );
-          // Continue with deletion even if summary cleanup fails
-        }
-      }
-
       // Delete all historical summaries for this chat
       const summaries = await ctx.db
         .query("chat_summaries")
         .withIndex("by_chat_id", (q) => q.eq("chat_id", args.chatId))
         .collect();

       for (const summary of summaries) {
         try {
           await ctx.db.delete(summary._id);
         } catch (error) {
           console.error(`Failed to delete summary ${summary._id}:`, error);
           // Continue with deletion even if summary cleanup fails
         }
       }

Option 2: Skip the latest_summary_id when iterating:

       for (const summary of summaries) {
+        if (summary._id === chat.latest_summary_id) {
+          continue; // Already deleted above
+        }
         try {
           await ctx.db.delete(summary._id);

712-738: Consider optimizing summary deletion to avoid potential double deletion.

Same issue as in deleteChat (lines 555-581): the latest summary may be deleted twice—once via latest_summary_id and again when iterating all summaries by chat_id. While error handling prevents failure, it's inefficient.

Apply the same optimization as suggested for deleteChat above: either query and delete all summaries in one pass without special-casing latest_summary_id, or skip latest_summary_id when iterating the results.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf8a4aa and 7162ffd.

📒 Files selected for processing (1)
  • convex/chats.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
convex/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

convex/**/*.{ts,tsx,js,jsx}: ALWAYS use the new Convex function syntax (query/mutation/action object with args/returns/handler) when defining Convex functions
When a Convex function returns null, include returns: v.null() and return null
Use internalQuery/internalMutation/internalAction for private functions; do not expose sensitive logic with public query/mutation/action
Do NOT register functions via the api or internal objects
ALWAYS provide args and returns validators for query, mutation, action and their internal variants; use v.null() when no value is returned
From Convex functions, use ctx.runQuery/ctx.runMutation/ctx.runAction to call other functions (not direct function calls)
Pass a FunctionReference (from api/internal) to ctx.runQuery/ctx.runMutation/ctx.runAction; do NOT pass the callee function directly
Only call an action from another action when crossing runtimes (e.g., V8 to Node); otherwise refactor shared code into helpers
Minimize calls from actions to queries/mutations to reduce race conditions; prefer fewer transactional boundaries
Organize public query/mutation/action functions under convex/ to align with file-based routing
For pagination, validate args with paginationOptsValidator and call .paginate(args.paginationOpts) on queries
Use v.int64() instead of deprecated v.bigint() for signed 64-bit integers
Use v.record() for record-like structures; v.map() and v.set() are not supported
Do NOT use query().filter(...); define an index in schema and use withIndex instead
Convex queries do not support .delete(); collect or iterate results and call ctx.db.delete(_id) for each
Use .unique() to fetch exactly one document and fail on multiples
When async-iterating query results, prefer for await (const row of query) over .collect() or .take(n)
Use ctx.db.replace to fully replace an existing document; ctx.db.patch to shallow-merge updates
Files defining actions that use Node built-ins must start with "use node"
Never access ctx.db from within an action
D...

Files:

  • convex/chats.ts
convex/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

When run-calling a function in the same file, add an explicit return type annotation on the await result to avoid TS circularity issues

Files:

  • convex/chats.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • convex/chats.ts
🧠 Learnings (1)
📚 Learning: 2025-10-03T13:15:14.787Z
Learnt from: CR
PR: hackerai-tech/hackerai#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T13:15:14.787Z
Learning: Applies to convex/**/*.{ts,tsx,js,jsx} : Use internalQuery/internalMutation/internalAction for private functions; do not expose sensitive logic with public query/mutation/action

Applied to files:

  • convex/chats.ts
🧬 Code graph analysis (1)
convex/chats.ts (1)
convex/tempStreams.ts (1)
  • validateServiceKey (4-8)
🔇 Additional comments (1)
convex/chats.ts (1)

46-46: LGTM! Return types correctly extended.

The latest_summary_id field additions properly type the new optional reference to chat summaries using v.optional(v.id("chat_summaries")), which aligns with the schema changes.

Also applies to: 130-130

- Remove file count display from PythonToolHandler UI
- Remove file paths from Python tool backend response
- Files are still saved to Convex but not reported in UI/output
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21f8c0e and df2d51e.

📒 Files selected for processing (1)
  • app/components/MessageActions.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • app/components/MessageActions.tsx
🔇 Additional comments (4)
app/components/MessageActions.tsx (4)

60-60: LGTM! Good addition of regeneration guard state.

The state variable is well-named and properly initialized to prevent concurrent regenerations.


91-95: LGTM! Well-implemented regeneration guard.

The early return and state update effectively prevent concurrent regenerations.


220-220: LGTM! Correctly wires up the new handler.

The onClick handler properly invokes handleRegenerate to ensure the regeneration guard is enforced.


221-221: LGTM! Appropriate disabled state update.

Including isRegenerating in the disabled condition provides proper visual feedback and prevents concurrent regeneration attempts.

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: 3

🧹 Nitpick comments (9)
lib/db/actions.ts (3)

299-308: Prefer “system” role for synthetic summary message

Using role "user" can skew downstream logic (e.g., user/assistant turn heuristics, tools). A “system” message better reflects metadata/context.

-            const summaryMessage: UIMessage = {
-              id: uuidv4(),
-              role: "user",
+            const summaryMessage: UIMessage = {
+              id: uuidv4(),
+              role: "system",
               parts: [
                 {
                   type: "text",
                   text: `<conversation_summary>\n${latestSummary.summary_text}\n</conversation_summary>`,
                 },
               ],
             };

288-297: Cutoff not found → duplicate context risk

If summaryUpToId isn’t found, you prepend the summary and still include all messages, duplicating summarized content.

Fallback to “no injection” when cutoffIndex < 0:

-            const messagesAfterCutoff =
-              cutoffIndex >= 0
-                ? truncatedFromLoop.slice(cutoffIndex + 1)
-                : truncatedFromLoop;
+            if (cutoffIndex < 0) {
+              // Can't safely align summary → skip injection
+              return { truncatedMessages: truncatedFromLoop, chat, isNewChat };
+            }
+            const messagesAfterCutoff = truncatedFromLoop.slice(cutoffIndex + 1);

610-639: saveChatSummary error handling OK; consider returning a boolean

You log and throw ChatSDKError—good. Returning void makes callers infer success by lack of throw; returning a boolean success can simplify branching and retries.

-export async function saveChatSummary({...}): Promise<void> {
+export async function saveChatSummary({...}): Promise<boolean> {
   try {
     await convex.mutation(...);
-    return;
+    return true;
   } catch (error) { ... }
}

Callers can check truthiness to gate UI events.

lib/utils/message-summarization.ts (4)

141-151: Use “system” role for the injected summary

Same rationale as in actions.ts: treat the synthetic summary as context, not a user turn.

-  const summaryMessage: UIMessage = {
-    id: uuidv4(),
-    role: "user",
+  const summaryMessage: UIMessage = {
+    id: uuidv4(),
+    role: "system",
     parts: [
       {
         type: "text",
         text: `<conversation_summary>\n${summaryText}\n</conversation_summary>`,
       },
     ],
   };

27-57: Token counting heuristic may overcount

Adding countTokens(message.role) and JSON.stringify for non-text parts can materially overcount vs. provider tokenization. It’s acceptable as a heuristic, but note it may trigger premature summarization.

  • Consider aligning with your truncateMessagesToTokenLimit logic.
  • Or use provider/tokenizer-aware estimation if available from the model adapter.

13-13: Type‑only import for SubscriptionTier

Prefer import type for TS‑only entities in server modules.

-import { SubscriptionTier } from "@/types";
+import type { SubscriptionTier } from "@/types";

As per coding guidelines.


99-118: Expose MESSAGES_TO_KEEP_UNSUMMARIZED as a param (optional)

Hard‑coding 2 works, but different models/contexts may need 3–4 turns. Consider making it a function param or config.

lib/api/chat-handler.ts (1)

275-314: Event sequencing and persistence coupling

Emitting “started” before persistence and “completed” after is correct. Consider emitting a “failed” event on error to keep UI consistent.

Add a writer.write({ type: "data-summarization", data: { status: "failed" }}) in the catch around saveChatSummary.

app/components/MessageEditor.tsx (1)

22-23: Token‑based guard on edited messages — good; small UX nits

  • Good: trim + token gating + plan‑aware toast.
  • Nit: use import type for SubscriptionTier if you pass it here later; and consider i18n for toast text.

Also applies to: 38-55

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df2d51e and e0a51e4.

📒 Files selected for processing (6)
  • app/components/MessageEditor.tsx (3 hunks)
  • lib/api/chat-handler.ts (7 hunks)
  • lib/db/actions.ts (7 hunks)
  • lib/token-utils.ts (5 hunks)
  • lib/utils/file-token-utils.ts (1 hunks)
  • lib/utils/message-summarization.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • lib/utils/message-summarization.ts
  • lib/api/chat-handler.ts
  • lib/db/actions.ts
  • lib/utils/file-token-utils.ts
  • lib/token-utils.ts
  • app/components/MessageEditor.tsx
🧬 Code graph analysis (5)
lib/utils/message-summarization.ts (2)
types/chat.ts (1)
  • SubscriptionTier (7-7)
lib/token-utils.ts (1)
  • getMaxTokensForSubscription (13-20)
lib/api/chat-handler.ts (3)
lib/utils/message-summarization.ts (1)
  • checkAndSummarizeIfNeeded (63-159)
lib/db/actions.ts (2)
  • saveChatSummary (611-639)
  • saveMessage (56-91)
convex/messages.ts (1)
  • saveMessage (93-201)
lib/db/actions.ts (3)
types/chat.ts (1)
  • SubscriptionTier (7-7)
convex/_generated/api.js (2)
  • api (21-21)
  • api (21-21)
lib/errors.ts (1)
  • ChatSDKError (36-74)
lib/utils/file-token-utils.ts (1)
types/chat.ts (1)
  • SubscriptionTier (7-7)
app/components/MessageEditor.tsx (2)
app/contexts/GlobalState.tsx (1)
  • useGlobalState (585-591)
lib/token-utils.ts (2)
  • countInputTokens (150-160)
  • getMaxTokensForSubscription (13-20)
🔇 Additional comments (12)
lib/api/chat-handler.ts (2)

461-470: Prepending summarization parts for assistant messages — LGTM

Parts-only augmentation is localized to assistant messages and won’t affect user messages; works with your Convex schema (parts: any[]).


103-111: Pass mode through to getMessagesByChatId — good

This enables skipFileTokens and, with the proposed fix in actions.ts, will gate summary injection by mode.

app/components/MessageEditor.tsx (1)

30-36: Auto‑focus and select all — LGTM

Nice productivity touch; safe guard with optional chaining is present.

lib/db/actions.ts (2)

255-259: All call sites verified and correctly updated

Both truncateMessagesWithFileTokens invocations (lines 254 and 340 in lib/db/actions.ts) correctly pass mode === "agent" as the skipFileTokens parameter. No additional call sites exist, confirming consistent implementation across the codebase.


641-652: All validators correctly defined and return type matches backend shape

Verification confirms:

  • chat_summaries schema properly defines summary_text and summary_up_to_message_id as v.string()
  • getLatestSummaryForBackend has explicit return type validation matching these fields
  • Frontend function correctly returns the backend shape or null on error
  • No type mismatches or validator issues
lib/utils/file-token-utils.ts (1)

90-115: All callers correctly pass skipFileTokens parameter

Both call sites in lib/db/actions.ts already properly pass mode === "agent" as the third argument to truncateMessagesWithFileTokens, aligning with the agent flow where files go to sandbox. No callers are missing this parameter.

lib/token-utils.ts (6)

5-20: LGTM! Clear naming and consistent subscription handling.

The renaming to MAX_TOKENS_PRO_AND_TEAM better reflects that both subscription tiers share the same limit, and the function logic correctly implements this equivalence.


23-24: Verify the impact of halving token limits.

Reducing STREAM_MAX_TOKENS and TOOL_DEFAULT_MAX_TOKENS from 4096 to 2048 is a significant change (50% reduction) that will affect streaming responses and tool output truncation. Ensure this change:

  • Doesn't break existing functionality or degrade user experience
  • Has been tested with typical streaming and tool usage scenarios
  • Aligns with the conversation summarization feature (possibly freeing up tokens for summaries)

28-29: LGTM! Good separation of truncation messages.

The file-specific truncation message provides clearer user feedback when file content is truncated.


53-69: LGTM! Cleaner token counting logic.

The refactoring to use countPartTokens with reduce is more maintainable and handles all message part types consistently. The logic correctly filters out reasoning blocks and sums tokens across all relevant parts.


74-98: LGTM! Simplified and correct truncation logic.

The direct use of maxTokens in the comparison is cleaner than maintaining a separate variable. The truncation logic correctly processes messages from newest to oldest and maintains proper token accounting.


103-173: LGTM! Proper integration with refactored utilities.

The remaining functions correctly integrate with the refactored token counting logic, and truncateOutput appropriately uses the new file-specific truncation message.

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: 0

♻️ Duplicate comments (1)
lib/token-utils.ts (1)

37-54: Improve type safety: UIMessagePart<any, any> still uses any generics.

While UIMessagePart<any, any> is better than plain any, the generic parameters still bypass TypeScript's type checking. The multiple type assertions on lines 42, 47, and 49 indicate this could be improved with proper typing.

Consider defining concrete types for the generic parameters or using a discriminated union. If the ai library exports specific part types, use those:

+// Define or import concrete types for message parts
+type TextPart = UIMessagePart<{ text: string }, {}>;
+type FilePart = UIMessagePart<{ fileId: Id<"files"> }, {}>;
+type OtherPart = UIMessagePart<any, any>;
+type SupportedMessagePart = TextPart | FilePart | OtherPart;
+
 const countPartTokens = (
-  part: UIMessagePart<any, any>,
+  part: SupportedMessagePart,
   fileTokens: Record<Id<"files">, number> = {},
 ): number => {

Alternatively, check if the ai library provides a more specific type you can import directly.

As per coding guidelines.

🧹 Nitpick comments (2)
lib/api/chat-handler.ts (1)

315-320: Consider adding context to error logs for better observability.

The generic "Error in prepareStep:" message doesn't indicate whether the error occurred during summarization or system prompt refresh. Adding context could help with debugging.

Consider logging the error context:

 } catch (error) {
-  console.error("Error in prepareStep:", error);
+  console.error("Error in prepareStep:", {
+    hasSummarized,
+    error: error instanceof Error ? error.message : String(error),
+  });
   return currentSystemPrompt
     ? { system: currentSystemPrompt }
     : {};
 }
lib/utils/stream-writer-utils.ts (1)

5-7: Consider tighter typing for the internal StreamWriter interface.

The write: (data: any) => void signature is very permissive. While functional, you could define a union of expected message types for better type safety.

Example:

type StreamMessage =
  | { type: "data-upload-status"; data: { message: string; isUploading: boolean }; transient: true }
  | { type: "data-summarization"; id: string; data: { status: string; message: string } };

type StreamWriter = {
  write: (data: StreamMessage) => void;
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0a51e4 and 3ab42b7.

📒 Files selected for processing (5)
  • lib/api/chat-handler.ts (8 hunks)
  • lib/db/actions.ts (7 hunks)
  • lib/token-utils.ts (5 hunks)
  • lib/utils/file-token-utils.ts (4 hunks)
  • lib/utils/stream-writer-utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • lib/db/actions.ts
  • lib/utils/stream-writer-utils.ts
  • lib/api/chat-handler.ts
  • lib/utils/file-token-utils.ts
  • lib/token-utils.ts
🧬 Code graph analysis (4)
lib/db/actions.ts (3)
types/chat.ts (1)
  • SubscriptionTier (7-7)
convex/_generated/api.js (2)
  • api (21-21)
  • api (21-21)
lib/errors.ts (1)
  • ChatSDKError (36-74)
lib/api/chat-handler.ts (5)
lib/utils/stream-writer-utils.ts (5)
  • writeUploadStartStatus (10-19)
  • writeUploadCompleteStatus (21-30)
  • writeSummarizationStarted (33-42)
  • writeSummarizationCompleted (44-53)
  • createSummarizationCompletedPart (55-65)
lib/utils/sandbox-file-utils.ts (1)
  • uploadSandboxFiles (90-118)
lib/utils/message-summarization.ts (1)
  • checkAndSummarizeIfNeeded (63-159)
lib/db/actions.ts (2)
  • saveChatSummary (612-640)
  • saveMessage (56-91)
convex/messages.ts (1)
  • saveMessage (93-201)
lib/utils/file-token-utils.ts (2)
convex/_generated/dataModel.d.ts (1)
  • Id (48-49)
types/chat.ts (1)
  • SubscriptionTier (7-7)
lib/token-utils.ts (1)
convex/_generated/dataModel.d.ts (1)
  • Id (48-49)
🔇 Additional comments (20)
lib/utils/file-token-utils.ts (3)

43-68: LGTM! Type safety improvements align with coding guidelines.

The update to use Record<Id<"files">, number> instead of Record<string, number> correctly follows the coding guidelines and improves type safety across the file token handling utilities.


98-115: LGTM! Well-designed parameter for agent mode.

The addition of the skipFileTokens parameter with a sensible default (false) maintains backward compatibility while enabling the new summarization workflow. The conditional logic correctly handles both paths: extracting and fetching file tokens when needed, or skipping entirely for agent mode.


120-136: LGTM! Type consistency maintained.

The update to precomputedFileTokens parameter type correctly aligns with the return type of getFileTokensByIds and maintains consistency across all file token utilities.

lib/token-utils.ts (8)

1-4: LGTM! Type imports and constant updates align with guidelines.

The addition of UIMessagePart and Id types, along with the renamed constant and updated subscription tier logic, are consistent with the PR objectives and coding guidelines.

Also applies to: 7-7, 18-19


24-25: LGTM! Token limit adjustments and new constant.

The reduced token limits (4096→2048) and new FILE_READ_TRUNCATION_MESSAGE constant align with the PR's token management improvements.

Also applies to: 29-30


59-75: LGTM! Token counting logic correctly handles file tokens.

The function properly uses Record<Id<"files">, number> per coding guidelines and correctly counts tokens for each message part while filtering out reasoning blocks.


80-104: LGTM! Truncation logic correctly enforces token limits.

The function properly uses Record<Id<"files">, number> and correctly truncates messages by keeping the newest messages within the token limit.


109-118: LGTM! Token counting function is correct.

The function properly uses Record<Id<"files">, number> per coding guidelines and correctly sums tokens across all messages.


123-139: LGTM! Content truncation logic is correct.

The function correctly uses the updated TOOL_DEFAULT_MAX_TOKENS limit and properly handles edge cases where the suffix exceeds the token budget.


156-166: LGTM! Input token counting is correct.

The function correctly counts tokens from both text input and uploaded files.


171-179: LGTM! Legacy wrapper function is correct.

The function properly delegates to truncateContent with the appropriate suffix based on mode.

lib/db/actions.ts (5)

19-19: LGTM: UUID import for summary message IDs.

The uuid import is appropriately used at line 301 to generate unique IDs for injected summary messages.


195-203: LGTM: Mode parameter properly typed.

The mode parameter is correctly typed as an optional union of "ask" | "agent" and properly threaded through for mode-aware token handling.


218-221: LGTM: Efficient conditional summary fetch.

The optional chaining and conditional query appropriately avoid unnecessary database calls when no summary exists.


283-317: Past review concern addressed via design clarification.

The previous review flagged the missing agent-mode gate for summary injection. The updated comments (lines 283-284) now explicitly document that summaries apply "regardless of current mode" because "summaries are only created in agent mode but provide value in any mode." This appears to be an intentional design decision that has been clarified in the code.


612-653: LGTM: Well-structured summary persistence functions.

Both functions implement appropriate error handling:

  • saveChatSummary throws ChatSDKError for critical persistence failures with detailed logging
  • getLatestSummary returns null for graceful degradation on query failures

The error handling strategy aligns well with their respective roles.

lib/api/chat-handler.ts (3)

7-7: LGTM: Summarization imports properly integrated.

All imports are appropriately used throughout the summarization flow and helper functions.

Also applies to: 34-34, 46-53


246-285: Past review concern fully addressed with correct persistence ordering.

The previous review flagged setting hasSummarized/finalMessages before persistence. The updated code now:

  1. Persists the summary first (lines 266-271)
  2. Updates state only after successful save (lines 273-275)
  3. Returns updated messages for the step

If saveChatSummary throws, the catch block at line 315 allows hasSummarized to remain false, enabling retry on the next step. This is the correct sequence.


420-428: LGTM: Summarization parts correctly prepended to assistant messages.

The logic appropriately prepends accumulated summarizationParts to assistant messages before persistence, preserving the existing parts array.

lib/utils/stream-writer-utils.ts (1)

1-65: LGTM: Clean utility with proper discriminated union typing.

The utility functions are well-structured with consistent patterns. Line 59 correctly uses as const for the type property in line with coding guidelines.

@rossmanko rossmanko merged commit 2fcf2d0 into main Oct 22, 2025
3 checks passed
This was referenced Feb 2, 2026
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.

2 participants